/**
 * Copyright (c) 2018 Dell Inc., or its subsidiaries. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 */ import * as React from "react" ; import { ClassNames , Styles } from "./ClassNames" ; import { classNames , allTrueOnKey } from "../utils" ; import { CheckBox } from "../forms/checkbox" ; import { RadioButton } from "../forms/radio" ; import { Button } from "../forms/button" ; import { Icon , Direction } from "../icon" ; import { Spinner , SpinnerSize } from "../spinner/Spinner" ; import { HideShowColumns } from "./HideShowColumns" ; import { DataGridColumnResize } from "./DataGridColumnResize" ; /**
 * General component description :
 * DataGrid :
 * Datagrids are for organizing large volumes of data that users can perform actions on.
 */ param { className } CSS param { style } CSS param { selectionType } row param { pagination } pagination param { columns } column param { rows } rows param { footer } footer param { onRowSelect } Function param { onSelectAll } Function param { hideSelectAll } when param { keyfield } field param { rowType } Expandable param { itemText } label param { pagination } pagination param { selectedRowCount } number param { dataqa } quality param { id } unique param { isLoading } if type DataGridProps = { className ? : string ; style ? : any ; selectionType ? : GridSelectionType ; columns : DataGridColumn [ ] ; rows ? : DataGridRow [ ] ; footer ? : DataGridFooter ; onRowSelect ? : ( selectedRow : DataGridRow ) => void ; onSelectAll ? : ( areAllSelected : boolean , selectedRows : DataGridRow [ ] ) => void ; hideSelectAll ? : boolean ; keyfield ? : string ; rowType ? : GridRowType ; itemText ? : string ; pagination ? : DataGridPaginationProps ; selectedRowCount ? : number ; dataqa ? : string ; id ? : string ; isLoading ? : boolean ; } ; param { columnName } column param { displayName } display param { sort } does param { className } CSS param { columns } column param { style } CSS param { filter } Filter param { isVisible } if param { width } Width export type DataGridColumn = { columnName : string ; displayName ? : any ; columnID ? : number ; sort ? : DataGridSort ; className ? : string ; style ? : any ; filter ? : React . ReactNode ; isVisible ? : boolean ; width ? : number ; } ; param { rowData } row param { rowID } unique param { isSelected } set param { className } CSS param { style } CSS param { expandableContent } Expandable param { detailPaneData } data param { disableRowSelection } If export type DataGridRow = { rowData : DataGridCell [ ] ; className ? : string ; style ? : any ; rowID ? : number ; isSelected ? : boolean ; expandableRowData ? : ExpandableRowDetails ; detailPaneData ? : DetailPaneData ; disableRowSelection ? : boolean ; } ; param { isLoading } if param { onRowExpand } callback param { onRowContract } callback param { expandableContent } content param { isExpanded } true param { hideRowExpandIcon } if export type ExpandableRowDetails = { isLoading ? : boolean ; onRowExpand ? : ( row : DataGridRow ) => Promise < any > ; onRowContract ? : ( row : DataGridRow ) => void ; expandableContent ? : any ; isExpanded ? : boolean ; hideRowExpandIcon ? : boolean ; } ; param { title } title param { hideDetailPane } if param { onOpenDetails } callback param { onCloseDetails } callback param { detailPaneContent } static param { isOpen } true export type DetailPaneData = { title ? : any ; hideDetailPane ? : boolean ; onOpenDetails ? : ( row : DataGridRow ) => Promise < any > ; onCloseDetails ? : ( row : DataGridRow ) => void ; detailPaneContent : any ; isOpen ? : boolean ; } ; param { cellData } cell param { className } CSS param { style } CSS param { cellDisplayData } Optional export type DataGridCell = { cellData : any ; cellDisplayData ? : JSX . Element ; columnName : string ; className ? : string ; style ? : any ; } ; param { footerData } Footer param { className } CSS param { style } CSS param { hideShowColBtn } Hide export type DataGridFooter = { footerData ? : any ; className ? : string ; style ? : any ; hideShowColumns ? : DataGridHideShowColumns ; showFooter : boolean ; } ; param { updateDataGridColumns } Function export type DataGridHideShowColumns = { hideShowColBtn : boolean ; updateDataGridColumns ? : ( columns : DataGridColumn [ ] ) => void ; } ; param { defaultSortOrder } if param { sortFunction } function param { isSorted } checks param { hideSort } if export type DataGridSort = { defaultSortOrder : SortOrder ; sortFunction : ( rows : DataGridRow [ ] , order : SortOrder , columnName : string ) => Promise < DataGridRow [ ] > ; isSorted ? : boolean ; hideSort ? : boolean ; } ; param { className } CSS param { style } CSS param { currentPage } Index param { pageSize } Number param { totalItems } Total param { lastPage } Index param { getPage } custom param { compactFooter } if type DataGridPaginationProps = { className ? : string ; style ? : any ; currentPage ? : number ; pageSize ? : number ; pageSizes ? : number [ ] ; totalItems : number ; compactFooter ? : boolean ; getPageData ? : ( pageIndex : number , pageSize : number ) => Promise < DataGridRow [ ] > ; } ; param { MULTI } for param { SINGLE } for export enum GridSelectionType { MULTI = "multi" , SINGLE = "single" } param { DESC } to param { ASC } to param { NONE } no export enum SortOrder { DESC = "descending" , ASC = "ascending " , NONE = "none" } param { EXPANDABLE } for param { COMPACT } for param { ROWS_WITH_DETAIL_PANE } for param { EXPANDABLE_ROWS_WITH_DETAIL_PANE } for param { COMPACT_ROWS_WITH_DETAIL_PANE } for export enum GridRowType { EXPANDABLE = "expandable" , COMPACT = "compact" , ROWS_WITH_DETAIL_PANE = "rows_with_detail_pane" , EXPANDABLE_ROWS_WITH_DETAIL_PANE = "expandable_rows_with_detail_pane" , COMPACT_ROWS_WITH_DETAIL_PANE = "compact_rows_with_detail_panes" } const isSelectedKey = "isSelected" ; const DEFAULT_ITEM_TEXT = "items" ; export const DEFAULT_COLUMN_WIDTH = 100 ; export const DEFAULT_CURRENT_PAGE_NUMBER = 1 ; export const DEFAULT_PAGE_SIZE = 10 ; export const DEFAULT_TOTAL_ITEMS = 0 ; param { selectAll } set param { allColumns } column param { allRows } row param { itemText } label param { pagination } pagination param { isLoading } if type DataGridState = { selectAll : boolean ; allColumns : DataGridColumn [ ] ; allRows : DataGridRow [ ] ; itemText : string ; pagination ? : DataGridPaginationState ; isLoading : boolean ; } ; type DataGridPaginationState = { currentPage : number ; pageSize : number ; totalItems : number ; firstItem : number ; lastItem : number ; totalPages : number ; pageSizes ? : number [ ] ; compactFooter ? : boolean ; } ; /**
 * DataGrid Componnet :
 * Displays data in grid format
 */ export class DataGrid extends React . PureComponent < DataGridProps , DataGridState > { private pageIndexRef = React . createRef < HTMLInputElement > ( ) ; private datagridTableRef = React . createRef < HTMLDivElement > ( ) ; constructor ( props ) { super ( props ) ; this . state = this . initializeDataGridState ( ) ; } componentDidUpdate ( prevProps ) { const { rows , columns , pagination } = this . props ; if ( rows && rows !== prevProps . rows ) { this . updateRows ( rows , pagination && pagination . totalItems ) ; } if ( columns && columns !== prevProps . columns ) { this . updateColumns ( columns ) ; } } initializeDataGridState = ( ) : DataGridState => { const { isLoading , itemText } = this . props ; const rows = this . initializeRowData ( ) ; const columns = this . initializeColumnData ( ) ; const dataGridState = { isLoading : isLoading || false , selectAll : this . isAllRowsSelected ( rows ) , allColumns : [ ... columns ] , allRows : [ ... rows ] , itemText : itemText || DEFAULT_ITEM_TEXT , pagination : this . initializePaginationData ( ) , } ; return dataGridState ; } ; initializeRowData = ( ) : DataGridRow [ ] => { const { rows } = this . props ; let updatedRows = [ ] ; if ( rows && rows . length ) { updatedRows = this . updateRowIDs ( rows ) ; updatedRows . forEach ( function ( row ) { const rowSelectionIsDisabled = row . disableRowSelection !== undefined ? row . disableRowSelection : false ; row . isSelected = ! rowSelectionIsDisabled && row . isSelected !== undefined ? row . isSelected : false ; } ) ; } return updatedRows ; } ; initializeColumnData = ( ) : DataGridColumn [ ] => { const { columns } = this . props ; let updatedColumns = [ ] ; if ( columns && columns . length ) { updatedColumns = this . updateColumnIDs ( this . setColumnVisibility ( columns ) ) ; updatedColumns . forEach ( col => { col . width = col . width ? col . width : DEFAULT_COLUMN_WIDTH ; } ) ; } return updatedColumns ; } ; private initializePaginationData ( ) { const { pagination } = this . props ; if ( pagination ) { const { currentPage , pageSize , totalItems , compactFooter , pageSizes } = pagination ; const currentPageNumber = currentPage || DEFAULT_CURRENT_PAGE_NUMBER ; const datagridPageSize = pageSize || DEFAULT_PAGE_SIZE ; const totalItemsInDatagrid = totalItems || DEFAULT_TOTAL_ITEMS ; const firstItem = this . getFirstItemIndex ( currentPageNumber , datagridPageSize ) ; const lastItem = this . getLastItemIndex ( datagridPageSize , totalItemsInDatagrid , firstItem ) ; const paginationState = { currentPage : currentPageNumber , pageSize : datagridPageSize , totalItems : totalItemsInDatagrid , pageSizes : pageSizes , compactFooter : compactFooter || false , firstItem : firstItem , lastItem : lastItem , totalPages : this . getTotalPages ( totalItemsInDatagrid , datagridPageSize ) , } ; return paginationState ; } } getSelectedRows = ( ) : DataGridRow [ ] => { const { allRows } = this . state ; return allRows . filter ( row => row . isSelected === true ) ; } ; getSelectionEnabledRows = ( allRows ) : DataGridRow [ ] => { return allRows . filter ( row => ! row . disableRowSelection ) ; } ; isAllRowsSelected = ( allRows ) : boolean => { const rows = this . getSelectionEnabledRows ( allRows ) ; if ( rows && rows . length ) { return allTrueOnKey ( rows , isSelectedKey ) ; } return false ; } ; getAllRows = ( ) => { return this . state . allRows ; } ; updateRows = ( rows , totalItems ? ) => { const updatedRows = this . updateRowIDs ( rows ) ; let { pagination } = this . state ; if ( pagination && totalItems !== undefined ) { const { pageSize , compactFooter } = pagination ; pagination . totalPages = this . getTotalPages ( totalItems , pageSize ) ; const currentPage = pagination . currentPage > pagination . totalPages ? 1 : pagination . currentPage ; const firstItem = this . getFirstItemIndex ( currentPage , pageSize ) ; const lastItem = this . getLastItemIndex ( pageSize , totalItems , firstItem ) ; pagination . firstItem = firstItem ; pagination . lastItem = lastItem ; pagination . currentPage = currentPage ; pagination . totalItems = totalItems ; if ( this . pageIndexRef . current ) { this . pageIndexRef . current . value = currentPage . toString ( ) ; } pagination . compactFooter = compactFooter !== undefined ? compactFooter : false ; } this . setState ( { allRows : [ ... updatedRows ] , selectAll : this . isAllRowsSelected ( updatedRows ) , pagination : pagination ? pagination : undefined , } ) ; } ; updateColumns = ( cols ) => { const { footer } = this . props ; const columnsWithVisibility = this . setColumnVisibility ( cols ) ; const columnsWithSort = this . setSortingState ( columnsWithVisibility ) ; const updatedCols = this . updateColumnIDs ( columnsWithSort ) ; this . setState ( { allColumns : [ ... updatedCols ] , } , ( ) => { footer && footer . hideShowColumns && footer . hideShowColumns . updateDataGridColumns && footer . hideShowColumns . updateDataGridColumns ( updatedCols ) ; } ) ; } ; updateColumnWidth = ( col ) => { const { allColumns } = this . state ; if ( col && col . columnID !== undefined ) { allColumns [ col . columnID ] . width = col . width ; this . setState ( { allColumns : [ ... allColumns ] , } ) ; } } ; hideLoader ( ) { this . setState ( { isLoading : false } ) ; } showLoader ( ) { this . setState ( { isLoading : true } ) ; } closeDetailPane = ( ) => { if ( this . isDetailPaneOpen ( ) ) { const row = this . getRowDataWithOpenDetailPane ( ) ; row && row . rowID !== undefined && this . handleDetailPaneToggle ( row . rowID ) ; } } ; private getTotalPages = ( totalItems , pageSize ) => { return Math . floor ( ( totalItems + pageSize - 1 ) / pageSize ) ; } ; private getFirstItemIndex = ( page , pageSize ) => { return page * pageSize - ( pageSize - 1 ) ; } ; private getLastItemIndex = ( pageSize , totalItems , firstItem ) => { return Math . min ( firstItem + pageSize - 1 , totalItems ) ; } ; private handleSelectPageSize = ( evt ) => { this . getPage ( this . state . pagination ! . currentPage , parseInt ( evt . target . value ) ) ; } ; private gotoFirstPage = ( ) => { const { pageSize } = this . state . pagination ! ; this . getPage ( 1 , pageSize ) ; } ; private gotoLastPage = ( ) => { const { pageSize , totalPages } = this . state . pagination ! ; this . getPage ( totalPages , pageSize ) ; } ; private gotoNextPage = ( ) => { const { pageSize , currentPage , totalPages } = this . state . pagination ! ; let nextPage = currentPage + 1 ; if ( nextPage > totalPages ) nextPage = totalPages ; this . getPage ( nextPage , pageSize ) ; } ; private gotoPreviousPage = ( ) => { const { pageSize , currentPage } = this . state . pagination ! ; let previousPage = currentPage - 1 ; if ( previousPage < 1 ) previousPage = 1 ; this . getPage ( previousPage , pageSize ) ; } ; private handlePageChangeOnBlur = ( evt ) => { const { pagination } = this . state ; const pageInputFieldValue = this . pageIndexRef . current && parseInt ( this . pageIndexRef . current . value ) ; const currentPageValue = pagination && pagination . currentPage ; if ( pageInputFieldValue !== currentPageValue ) { this . handlePageChange ( ) ; } } ; private handlePageChangeOnKeyDown = ( evt ) => { const keyCode = evt . keyCode ; if ( keyCode == 13 || keyCode == 9 ) { this . handlePageChange ( ) ; } } ; private handlePageChange = ( ) => { const { pageSize , currentPage } = this . state . pagination ! ; const pageIndex = this . pageIndexRef . current && parseInt ( this . pageIndexRef . current . value ) ; if ( pageIndex ) { if ( isNaN ( pageIndex ) ) { this . pageIndexRef . current ! . value = currentPage . toString ( ) ; } else { this . getPage ( pageIndex , pageSize ) ; } } } ; private getPage ( pageIndex , pageSize ) { if ( this . state . pagination && this . props . pagination ) { this . showLoader ( ) ; const { totalItems } = this . state . pagination ; const { getPageData } = this . props . pagination ; const totalPages = this . state . pagination . pageSize !== pageSize ? this . getTotalPages ( totalItems , pageSize ) : this . state . pagination . totalPages ; if ( pageIndex > totalPages ! && totalPages ) { pageIndex = totalPages ; } if ( pageIndex < 1 ) { pageIndex = 1 ; } if ( this . pageIndexRef . current ) { this . pageIndexRef . current . value = pageIndex . toString ( ) ; } if ( getPageData ) { var firstItem = this . getFirstItemIndex ( pageIndex , pageSize ) ; var lastItem = this . getLastItemIndex ( pageSize , totalItems , firstItem ) ; let paginationState = this . state . pagination ; if ( paginationState ) { paginationState . pageSize = this . state . pagination . pageSize !== pageSize ? pageSize : this . state . pagination . pageSize ; paginationState . currentPage = pageIndex ; paginationState . firstItem = firstItem ; paginationState . lastItem = lastItem ; paginationState . totalPages = this . getTotalPages ( totalItems , pageSize ) ; } getPageData ( pageIndex , pageSize ) . then ( ( data ) => { const rows = this . updateRowIDs ( data ) ; this . setState ( { allRows : [ ... rows ] , pagination : paginationState , selectAll : this . isAllRowsSelected ( rows ) , } , ( ) => this . closeDetailPane ( ) ) ; this . hideLoader ( ) ; } ) ; } } } private toggleExpand ( rowID ) { let { allRows } = this . state ; let { expandableRowData } = allRows [ rowID ] ; if ( expandableRowData ) { expandableRowData . isExpanded = ! expandableRowData . isExpanded ; const { onRowExpand , isExpanded , onRowContract } = expandableRowData ; if ( onRowExpand && isExpanded ) { expandableRowData ! . isLoading = true ; allRows [ rowID ] . expandableRowData = expandableRowData ; this . setState ( { allRows : [ ... allRows ] } , ( ) => { onRowExpand ( allRows [ rowID ] ) . then ( ( content ) => { expandableRowData ! . expandableContent = content ; expandableRowData ! . isLoading = false ; allRows [ rowID ] . expandableRowData = expandableRowData ; this . setState ( { allRows : [ ... allRows ] } ) ; } ) ; } ) ; } else if ( onRowContract && ! isExpanded ) { this . setState ( { allRows : [ ... allRows ] } , ( ) => { onRowContract ( allRows [ rowID ] ) ; } ) ; } else { allRows [ rowID ] . expandableRowData = expandableRowData ; this . setState ( { allRows : [ ... allRows ] } ) ; } } } public handleDetailPaneToggle = ( rowID ) => { let { allRows } = this . state ; let { detailPaneData } = allRows [ rowID ] ; if ( detailPaneData ) { detailPaneData . isOpen = ! detailPaneData . isOpen ; const { isOpen , onOpenDetails , onCloseDetails } = detailPaneData ; const updatedRows = allRows . map ( ( row ) => { if ( row . detailPaneData ) { row . detailPaneData . isOpen = false ; } return row ; } ) ; if ( isOpen ) { onOpenDetails && onOpenDetails ( allRows [ rowID ] ) . then ( ( content ) => { if ( content ) { updatedRows [ rowID ] . detailPaneData ! . detailPaneContent = content ; } } ) ; updatedRows [ rowID ] . detailPaneData ! . isOpen = isOpen ; } this . setState ( { allRows : [ ... updatedRows ] , } , ( ) => ! isOpen && onCloseDetails && onCloseDetails ( allRows [ rowID ] ) ) ; } } ; private handleSelectAll = ( evt ) => { let { allRows , selectAll } = this . state ; const { onSelectAll } = this . props ; allRows . forEach ( row => { if ( ! row . disableRowSelection ) { row . isSelected = ! selectAll ; } } ) ; this . setState ( { selectAll : ! selectAll , allRows : [ ... allRows ] , } , ( ) => onSelectAll && onSelectAll ( ! selectAll , allRows ) ) ; } ; private handleSelectSingle = ( evt , rowID ) => { let { allRows } = this . state ; const { onRowSelect , selectionType } = this . props ; let selectedRow ; allRows . forEach ( row => { if ( row . rowID === rowID ) { row . isSelected = ! row . isSelected ; selectedRow = row ; } else if ( selectionType === GridSelectionType . SINGLE ) { row . isSelected = false ; } } ) ; this . setState ( { allRows : [ ... allRows ] , selectAll : this . isAllRowsSelected ( allRows ) , } , ( ) => onRowSelect && onRowSelect ( selectedRow ) ) ; } ; private handleSort = ( evt , columnName , columnID , sortFunction , defaultSortOrder ) => { this . showLoader ( ) ; const { allRows , allColumns } = this . state ; if ( columnID != undefined ) { allColumns . forEach ( col => { if ( col . sort ) { col . sort . isSorted = false ; } } ) ; let nextSortOrder = defaultSortOrder === SortOrder . NONE || defaultSortOrder === SortOrder . DESC ? SortOrder . ASC : SortOrder . DESC ; sortFunction ( allRows , nextSortOrder , columnName ) . then ( ( data ) => { const rows = this . updateRowIDs ( data ) ; allColumns [ columnID ] . sort ! . defaultSortOrder = nextSortOrder ; allColumns [ columnID ] . sort ! . isSorted = true ; this . setState ( { allRows : [ ... rows ] , allColumns : [ ... allColumns ] , } , ( ) => this . closeDetailPane ( ) ) ; this . hideLoader ( ) ; } ) ; } } ; private updateRowIDs ( rows ) { if ( rows && rows . length ) { rows . map ( ( row , index ) => { row . rowID = index ; } ) ; } return rows ; } private updateColumnIDs ( columns ) { columns . map ( ( column , index ) => { column [ "columnID" ] = index ; } ) ; return columns ; } private setColumnVisibility ( columns ) { columns . map ( ( column , index ) => { column [ "isVisible" ] = column . isVisible !== undefined ? column . isVisible : true ; } ) ; return columns ; } private getColObject ( columnName ) { const { allColumns } = this . state ; const column = allColumns . find ( col => col . columnName === columnName ) ; return column ; } private getColWidth ( columnName ) { const column = this . getColObject ( columnName ) ; return column && column . width ; } private isColVisible ( columnName ) { const column = this . getColObject ( columnName ) ; const isColumnVisible = this . isDetailPaneOpen ( ) && column && column . columnID !== this . getFirstVisibleColumn ( ) ! . columnID ? false : column && column . isVisible ; return isColumnVisible ; } private setSortingState ( columns ) { const { allColumns } = this . state ; columns . map ( ( column , index ) => { if ( column . sort ) { const col = allColumns . find ( ( { columnName } ) => columnName === column . columnName ) ; if ( col && col . sort ) { col . sort . hideSort = column . sort . hideSort ; column . sort = col . sort ; } } else { column . sort = undefined ; } } ) ; return columns ; } private getFirstVisibleColumn = ( ) => { const { allColumns } = this . state ; return allColumns . find ( ( column ) => column . isVisible === true ) ; } ; private isDatagridWithDetailPane = ( ) : boolean => { const { rowType } = this . props ; return rowType ? rowType === GridRowType . ROWS_WITH_DETAIL_PANE || rowType === GridRowType . EXPANDABLE_ROWS_WITH_DETAIL_PANE || rowType === GridRowType . COMPACT_ROWS_WITH_DETAIL_PANE : false ; } ; private getRowDataWithOpenDetailPane = ( ) => { if ( this . isDatagridWithDetailPane ( ) ) { return this . state . allRows . find ( ( row ) => row . detailPaneData && row . detailPaneData . isOpen === true ) ; } } ; private isDetailPaneOpen = ( ) : boolean => { return this . isDatagridWithDetailPane ( ) && this . getRowDataWithOpenDetailPane ( ) !== undefined ; } ; private buildDetailPaneToggleIcon ( { rowID , detailPaneData } ) { const { id } = this . props ; const { isOpen , hideDetailPane } = detailPaneData ? detailPaneData : { isOpen : false , hideDetailPane : false , } ; return ( < div className = { classNames ( [ ClassNames . DATAGRID_DETAIL_CARET , ClassNames . DATAGRID_FIXED_COLUMN , ClassNames . DATAGRID_CELL , ClassNames . DATAGRID_NG_STAR_INSERTED , ] ) } > { ! hideDetailPane && ( < Button key = { `${ id }-${ "detail-toggle" }-${ rowID }` } defaultBtn = { false } onClick = { ( ) => rowID !== undefined && this . handleDetailPaneToggle ( rowID ) } aria - haspopup = "dialog" className = { classNames ( [ ClassNames . DATAGRID_DETAIL_CARET_BUTTON , isOpen && ClassNames . IS_OPEN ] ) } aria - expanded = { isOpen } aria - controls = { `clr-id-${ rowID }` } icon = { { shape : "angle-double" , className : ClassNames . DATAGRID_DETAIL_CARET_ICON , dir : isOpen ? Direction . LEFT : Direction . RIGHT , } } />)} < /div>); } private buildExpandableCell ( { rowID , expandableRowData } ) { const { id } = this . props ; const { hideRowExpandIcon , isExpanded , isLoading } = expandableRowData ? expandableRowData : { hideRowExpandIcon : false , isExpanded : false , isLoading : false , } ; return ( < div className = { ClassNames . DATAGRID_EXPANDABLE_CARET } role = "gridcell" key = { rowID } > { isLoading ? ( < Spinner size = { SpinnerSize . SMALL } / > ) : ( ! hideRowExpandIcon && ( < Button key = { `${ id }-${ "expand" }-${ rowID }` } className = { ClassNames . DATAGRID_EXPANDABLE_CARET_BUTTON } onClick = { ( ) => rowID !== undefined && this . toggleExpand ( rowID ) } icon = { { shape : "caret" , className : ClassNames . DATAGRID_EXPANDABLE_CARET_ICON , dir : isExpanded ? Direction . DOWN : Direction . RIGHT , } } />))} < /div>); } private buildSelectColumn ( ) { const { selectionType , id , hideSelectAll } = this . props ; const { selectAll } = this . state ; return ( < div role = "columnheader" className = { classNames ( [ ClassNames . DATAGRID_COLUMN , ClassNames . DATAGRID_SELECT , ClassNames . DATAGRID_FIXED_COLUMN , ClassNames . DATAGRID_NG_STAR_INSERTED , ] ) } > < span className = { ClassNames . DATAGRID_COLUMN_TITLE } > { selectionType === GridSelectionType . MULTI && ! hideSelectAll && ( < div className = { classNames ( [ ClassNames . CLR_CHECKBOX_WRAPPER , ClassNames . DATAGRID_NG_STAR_INSERTED , ] ) } > < CheckBox id = { `${ id }-datagrid-select-all` } onChange = { evt => this . handleSelectAll ( evt ) } ariaLabel = "Select All" className = { ClassNames . CLR_SELECT } checked = { selectAll !== undefined ? selectAll : undefined } /> < /div>)} < /span> < div className = { ClassNames . DATAGRID_COLUMN_SEPARATOR } / > < /div>); } private buildEmptyColumn ( ) { return ( < div role = "columnheader" className = { classNames ( [ ClassNames . DATAGRID_COLUMN , ClassNames . DATAGRID_SELECT , ClassNames . DATAGRID_FIXED_COLUMN , ] ) } > < span className = { ClassNames . DATAGRID_COLUMN_TITLE } /> < div className = { ClassNames . DATAGRID_COLUMN_SEPARATOR } / > < /div>); } private buildSelectCell ( row ) { const { selectionType , id } = this . props ; const wrapperClassName = selectionType === GridSelectionType . MULTI ? ClassNames . CLR_CHECKBOX_WRAPPER : ClassNames . CLR_RADIO_WRAPPER ; return ( < div role = "gridcell" className = { classNames ( [ ClassNames . DATAGRID_SELECT , ClassNames . DATAGRID_FIXED_COLUMN , ClassNames . DATAGRID_CELL , ClassNames . DATAGRID_NG_STAR_INSERTED , ] ) } > < div className = { classNames ( [ wrapperClassName , ClassNames . DATAGRID_NG_STAR_INSERTED ] ) } > { ! row . disableRowSelection && ( selectionType === GridSelectionType . MULTI ? ( < CheckBox id = { `${ id }-${ row . rowID }-select-checkbox` } ariaLabel = "Select" className = { ClassNames . CLR_SELECT } onChange = { evt => this . handleSelectSingle ( evt , row . rowID ) } checked = { row . isSelected !== undefined ? row . isSelected : undefined } / > ) : ( < RadioButton value = { row . rowID } id = { `${ id }-${ row . rowID }-select-radio` } className = { ClassNames . CLR_SELECT } onChange = { evt => this . handleSelectSingle ( evt , row . rowID ) } checked = { row . isSelected !== undefined ? row . isSelected : undefined } / > ) ) } < /div> < /div>); } private buildDataGridBody ( ) { const { allRows } = this . state ; return ( < div className = { ClassNames . DATAGRID } > < div className = { ClassNames . DATAGRID_TABLE_WRAPPER } > < div ref = { this . datagridTableRef } className = { ClassNames . DATAGRID_TABLE } role = "grid" > { this . buildDataGridHeader ( ) } { allRows . map ( ( row , index ) => { return this . buildDataGridRow ( row , index ) ; } ) } { this . buildPlaceHolderContainer ( ) } < /div> < /div> < /div>); } private buildPlaceHolderContainer ( ) { const { allRows } = this . state ; return ( < div className = { classNames ( [ ClassNames . DATAGRID_PLACEHOLDER_CONTAINER , ClassNames . DATAGRID_NG_STAR_INSERTED , ] ) } > < div className = { classNames ( [ ClassNames . DATAGRID_PLACEHOLDER , allRows . length === 0 && ClassNames . DATAGRID_EMPTY , "clr-align-items-center" , "clr-justify-content-center" , ] ) } > { allRows . length === 0 && this . buildEmptyPlaceholder ( ) } < /div> < /div>); } private buildEmptyPlaceholder ( ) { const { itemText } = this . state ; const placeholderText = "No " + itemText + " found!" ; return ( < React . Fragment > < div className = { classNames ( [ ClassNames . DATAGRID_PLACEHOLDER_IMG , ClassNames . DATAGRID_NG_STAR_INSERTED ] ) } / > { placeholderText } < /React.Fragment>); } private buildDataGridHeader ( ) { const { selectionType , rowType } = this . props ; const { allColumns } = this . state ; const showEmptyHeader = rowType && rowType !== GridRowType . COMPACT ? true : false ; return ( < div className = { ClassNames . DATAGRID_HEADER } role = "rowgroup" > < div className = { ClassNames . DATAGRID_ROW } role = "row" > < div className = { ClassNames . DATAGRID_ROW_MASTER } > < div className = { ClassNames . DATAGRID_ROW_STICKY } > { selectionType && this . buildSelectColumn ( ) } { rowType && rowType === GridRowType . EXPANDABLE_ROWS_WITH_DETAIL_PANE && this . buildEmptyColumn ( ) } { showEmptyHeader && this . buildEmptyColumn ( ) } < /div> < div className = { ClassNames . DATAGRID_ROW_SCROLLABLE } > { allColumns && allColumns . map ( ( column , index ) => { const showColumn = this . isDetailPaneOpen ( ) && column . columnID !== this . getFirstVisibleColumn ( ) ! . columnID ? false : column . isVisible ; return showColumn ? this . buildDataGridColumn ( column , index ) : undefined ; } ) } < /div> < /div> < /div> < /div>); } private buildDataGridColumn ( column , index ) { const { columnName , displayName , columnID , className , style , sort , filter , width } = column ; const columnHeight = this . datagridTableRef && this . datagridTableRef . current && this . datagridTableRef . current . clientHeight ; const hideSort = sort && sort . hideSort !== undefined ? sort . hideSort : false ; return ( < div role = "columnheader" className = { classNames ( [ ClassNames . DATAGRID_COLUMN , ClassNames . DATAGRID_NG_STAR_INSERTED , className ] ) } aria - sort = "none" style = { { ... style , width : width + "px" } } key = { "col-" + index } > < div className = { ClassNames . DATAGRID_COLUMN_FLEX } > { sort !== undefined && ! hideSort ? ( < Button key = "sort" defaultBtn = { false } className = { classNames ( [ ClassNames . DATAGRID_COLUMN_TITLE , ClassNames . DATAGRID_NG_STAR_INSERTED , ] ) } onClick = { evt => this . handleSort ( evt , columnName , columnID , sort . sortFunction , sort . defaultSortOrder ) } > { displayName ? displayName : columnName } { sort . isSorted && sort . defaultSortOrder !== SortOrder . NONE && ( < Icon shape = { sort . defaultSortOrder == SortOrder . DESC ? "arrow down" : "arrow up" } className = { classNames ( [ ClassNames . DATAGRID_SORT_ICON , ClassNames . DATAGRID_NG_STAR_INSERTED , ] ) } / > ) } < /Button>) : (<span className={ClassNames.DATAGRID_COLUMN_TITLE}> { displayName ? displayName : columnName } < /span>)} { filter && filter } < DataGridColumnResize height = { columnHeight } column = { column } updateColumn = { this . updateColumnWidth } / > < /div> < /div>); } private buildDataGridRow ( row , index ) { const { rowType } = this . props ; const { className , style , isSelected , disableRowSelection , detailPaneData } = row ; const isExpandableRow = rowType ? rowType === GridRowType . EXPANDABLE || rowType === GridRowType . EXPANDABLE_ROWS_WITH_DETAIL_PANE : false ; const isRowWithDetailPane = ( row && row . detailPaneData && this . isDatagridWithDetailPane ( ) ) || false ; let rowStyle = style ; if ( index === 0 ) { rowStyle = { ... style , borderTop : "none" } ; } return ( < div role = "rowgroup" className = { classNames ( [ ClassNames . DATAGRID_ROW , ClassNames . DATAGRID_NG_STAR_INSERTED , ! disableRowSelection && isSelected && ClassNames . DATAGRID_SELECTED , isRowWithDetailPane && detailPaneData && detailPaneData . isOpen && ClassNames . DATAGRID_ROW_DETAIL_OPEN , className , ] ) } aria - owns = { "clr-dg-row" + index } style = { rowStyle } key = { "row-" + index } > { isExpandableRow ? ( < div className = { classNames ( [ `ng-tns-c96-${ index } `,
                    isExpandableRow && ClassNames.DATAGRID_EXPAND_ANIMATION,
                ])}>
                        {this.buildRowCells(row, isExpandableRow, isRowWithDetailPane)}
                    </div>) : (this.buildRowCells(row, isExpandableRow, isRowWithDetailPane))}
            </div>);
    }
    // Function to build cells of single row
    private buildRowCells(row, isExpandableRow, isRowWithDetailPane) {
        const { rowData } = row;
        const { selectionType } = this.props;
        return (<div className={classNames([ClassNames.DATAGRID_ROW_MASTER, ClassNames.DATAGRID_NG_STAR_INSERTED])} role="row">
                <div className={ClassNames.DATAGRID_ROW_STICKY}>
                    {selectionType && this.buildSelectCell(row)}
                    {isExpandableRow && this.buildExpandableCell(row)}
                    {isRowWithDetailPane && this.buildDetailPaneToggleIcon(row)}
                </div>
                <div className={ClassNames.DATAGRID_ROW_SCROLLABLE}>
                    <div className={ClassNames.DATAGRID_SCROLLING_CELLS}>
                        <div className={ClassNames.DATAGRID_NG_STAR_INSERTED}/>
                        <div className={ClassNames.DATAGRID_NG_STAR_INSERTED}/>
                        {rowData &&
                rowData.map((cell, index) => {
                    return this.buildDataGridCell(cell.cellData, index, cell.columnName, cell.className, cell.style, cell.cellDisplayData);
                })}
                    </div>
                    {/* //Insert Expandable item view */}
                    {isExpandableRow && this.buildExpandableRow(row)}
                </div>
            </div>);
    }
    private buildExpandableRow({ expandableRowData }) {
        const { expandableContent, isExpanded, isLoading } = expandableRowData
            ? expandableRowData
            : {
                expandableContent: undefined,
                isExpanded: false,
                isLoading: false,
            };
        const showExpandableContent = expandableContent && isExpanded && !isLoading;
        return (<React.Fragment>
                {showExpandableContent && (<div className={classNames([ClassNames.DATAGRID_ROW_FLEX])}>
                        <div className={ClassNames.CLR_SR_ONLY}/>
                        {expandableContent}
                        <div className={ClassNames.CLR_SR_ONLY}/>
                    </div>)}
            </React.Fragment>);
    }
    // Function to build detail pane DOM for row
    private buildDetailPaneForRow() {
        const row = this.getRowDataWithOpenDetailPane();
        if (row) {
            const rowID = row.rowID;
            const { title, detailPaneContent, isOpen } = row.detailPaneData
                ? row.detailPaneData
                : {
                    title: undefined,
                    detailPaneContent: undefined,
                    isOpen: false,
                };
            const showDetailPane = isOpen && detailPaneContent;
            return (<React.Fragment>
                    {showDetailPane && (<div className={classNames([
                        ClassNames.DATAGRID_DETAIL_PANE,
                        ClassNames.DATAGRID_NG_STAR_INSERTED,
                    ])}>
                            <span tabIndex={0} className={classNames([
                        ClassNames.OFFSCREEN_FOCUS_REBOUNDER,
                        ClassNames.DATAGRID_NG_STAR_INSERTED,
                    ])}/>
                            <div role="dialog" aria-modal="true" className={classNames([
                        ClassNames.DATAGRID_DETAIL_PANE_CONTENT,
                        ClassNames.DATAGRID_NG_STAR_INSERTED,
                    ])} id={` $ { "clr-id-" } - $ { rowID } `} aria-describedby={` clr - id - $ { rowID } - title `} tabIndex={-1}>
                                <div className={ClassNames.CLR_SR_ONLY}/>
                                <div className={ClassNames.DATAGRID_DETAIL_HEADER}>
                                    <div className={ClassNames.DATAGRID_DETAIL_HEADER_TITLE} id={` clr - id - $ { rowID } - title `}>
                                        {title}
                                    </div>
                                    <div className={ClassNames.DATAGRID_DETAIL_PANE_CLOSE}>
                                        <Button aria-label="Close" className={ClassNames.PAGINATION_NEXT} icon={{ shape: "close" }} onClick={() => rowID !== undefined && this.handleDetailPaneToggle(rowID)}/>
                                    </div>
                                </div>
                                {/* close header*/}
                                <div className={ClassNames.DATAGRID_DETAIL_BODY}>
                                    <div className={ClassNames.CLR_DG_DETAIL_BODY_WRAPPER}> {detailPaneContent} </div>
                                </div>
                                {/* close body */}
                                <div className={ClassNames.CLR_SR_ONLY}/>
                            </div>
                            <span tabIndex={0} className={classNames([
                        ClassNames.OFFSCREEN_FOCUS_REBOUNDER,
                        ClassNames.DATAGRID_NG_STAR_INSERTED,
                    ])}/>
                        </div>)}
                </React.Fragment>);
        }
    }
    // function to build datagrid cell
    private buildDataGridCell(cellData, index, columnName, className?, style?, cellDisplayData?) {
        const columnObj = this.getColObject(columnName);
        const width = this.getColWidth(columnName);
        const isColVisible = this.isColVisible(columnName);
        return (<div role="gridcell" key={"cell-" + index} className={classNames([
                ClassNames.DATAGRID_CELL,
                ClassNames.DATAGRID_NG_STAR_INSERTED,
                isColVisible !== undefined && !isColVisible && ClassNames.DATAGRID_HIDDEN_COLUMN,
                columnObj && columnObj.className,
                className,
            ])} style={{ ...style, width: width + "px" }}>
                {cellDisplayData || cellData}
            </div>);
    }
    // Function to build pageSizes select
    private buildPageSizesSelect() {
        const { pageSizes, pageSize } = this.state.pagination!;
        const { itemText } = this.state;
        return (<div className={ClassNames.PAGINATION_SIZE}>
                <div _ngcontent-clarity-c8="">
                    {` $ { itemText } $ { " per page" } `}
                    <div className={classNames([ClassNames.CLR_SELECT_WRAPPER])}>
                        <select className={classNames([ClassNames.CLR_PAGE_SIZE_SELECT])} onChange={evt => this.handleSelectPageSize(evt)} defaultValue={pageSize}>
                            {pageSizes!.map((size, index) => {
                return (<option key={index} value={size}>
                                        {size}
                                    </option>);
            })}
                        </select>
                    </div>
                </div>
            </div>);
    }
    private buildCompactPageButtons() {
        const { currentPage, totalPages } = this.state.pagination!;
        return (<div className={classNames([ClassNames.PAGINATION_LIST])}>
                <Button key="left-compact" className={ClassNames.PAGINATION_PREVIOUS} icon={{ shape: "angle left" }} disabled={currentPage == 1 ? true : false} onClick={this.gotoPreviousPage}/>
                <span>{currentPage}</span>
                <Button key="right-compact" className={ClassNames.PAGINATION_NEXT} icon={{ shape: "angle right" }} disabled={currentPage === totalPages ? true : false} onClick={this.gotoNextPage}/>
            </div>);
    }
    // Function to build Next, previous, last and first page buttons
    private buildPageButtons() {
        const { currentPage, totalPages } = this.state.pagination!;
        return (<div className={classNames([ClassNames.PAGINATION_LIST])}>
                <Button key="down" className={ClassNames.PAGINATION_FIRST} icon={{ shape: "step-forward-2 down" }} disabled={currentPage == 1 ? true : false} onClick={this.gotoFirstPage}/>
                <Button key="left" className={ClassNames.PAGINATION_PREVIOUS} icon={{ shape: "angle left" }} disabled={currentPage == 1 ? true : false} onClick={this.gotoPreviousPage}/>
                <input className={ClassNames.PAGINATION_CURRENT} size={2} defaultValue={currentPage.toString()} type="text" ref={this.pageIndexRef} aria-label="Current Page" onBlur={evt => this.handlePageChangeOnBlur(evt)} onKeyDown={evt => this.handlePageChangeOnKeyDown(evt)}/>
                &nbsp;/&nbsp;<span aria-label="Total Pages">{totalPages}</span>
                <Button key="right" className={ClassNames.PAGINATION_NEXT} icon={{ shape: "angle right" }} disabled={currentPage === totalPages ? true : false} onClick={this.gotoNextPage}/>
                <Button key="up" className={ClassNames.PAGINATION_LAST} icon={{ shape: "step-forward-2 up" }} disabled={currentPage == totalPages ? true : false} onClick={this.gotoLastPage}/>
            </div>);
    }
    // function to build datagrid pagination footer
    private buildDataGridPagination() {
        const { className, style, compactFooter } = this.props.pagination!;
        const { itemText } = this.state;
        const showCompactFooter = compactFooter || this.isDetailPaneOpen();
        let { totalItems, firstItem, lastItem, pageSize, pageSizes } = this.state.pagination!;
        if (totalItems === 0) {
            firstItem = lastItem = 0;
        }
        const paginationLabel = showCompactFooter
            ? firstItem + "-" + lastItem + " / " + totalItems
            : firstItem + "-" + lastItem + " of " + totalItems + " " + itemText;
        return (<div _ngcontent-clarity-c8="" style={style} className={classNames([ClassNames.DATAGRID_PAGINATION, className])}>
                {!showCompactFooter && pageSizes && totalItems >= pageSize && this.buildPageSizesSelect()}

                {showCompactFooter ? (<div className={ClassNames.DATAGRID_NG_STAR_INSERTED} style={Styles.PAGINATION_DESCRIPTION_COMPACT}>
                        {paginationLabel}
                    </div>) : (<div className={classNames([ClassNames.PAGINATION_DESC])}>{paginationLabel}</div>)}

                {showCompactFooter ? this.buildCompactPageButtons() : this.buildPageButtons()}
            </div>);
    }
    // function to build Hide and show columns menu
    private buildHideShowColumnsBtn() {
        const { allColumns } = this.state;
        return <HideShowColumns columns={allColumns} updateColumns={this.updateColumns}/>;
    }
    // function to build selected row count
    private buildSelectedRowCount() {
        const { selectedRowCount, selectionType } = this.props;
        const showSelectedRowsCount = selectedRowCount && selectedRowCount > 0 && selectionType === GridSelectionType.MULTI ? true : false;
        return showSelectedRowsCount ? (<div className={classNames([ClassNames.DATAGRID_FORM_CONTROL, ClassNames.DATAGRID_NG_STAR_INSERTED])}>
                <div className={ClassNames.DATAGRID_FOOTER_CHECKBOX}>
                    <CheckBox checked disabled label={selectedRowCount!.toString()}/>
                </div>
            </div>) : (<React.Fragment />);
    }
    private buildFooterContent() {
        const { footer } = this.props;
        const { allRows, itemText } = this.state;
        const footerDescription = allRows.length.toString() + " " + itemText;
        let content;
        if (footer !== undefined) {
            content = footer.footerData !== undefined ? footer.footerData : footerDescription;
        }
        return <div> {content} </div>;
    }
    // function to build datagrid footer
    private buildDataGridFooter() {
        const { footer } = this.props;
        const { pagination } = this.state;
        let renderPaginationFooter = false;
        if (pagination) {
            const { totalItems, pageSize } = pagination;
            if (totalItems && pageSize && totalItems >= pageSize) {
                renderPaginationFooter = true;
            }
        }
        return (<div className={` $ { ClassNames . DATAGRID_FOOTER } $ { footer && footer . className && footer . className } `} style={footer && footer.style && footer.style}>
                {footer &&
                footer.hideShowColumns &&
                footer.hideShowColumns.hideShowColBtn &&
                !this.isDetailPaneOpen() &&
                this.buildHideShowColumnsBtn()}

                {this.buildSelectedRowCount()}

                <div className={ClassNames.DATAGRID_FOOTER_DESC}>
                    {renderPaginationFooter ? this.buildDataGridPagination() : this.buildFooterContent()}
                </div>
            </div>);
    }
    buildDataGridSpinner() {
        return (<div className={classNames([ClassNames.DATAGRID_SPINNER, ClassNames.DATAGRID_NG_STAR_INSERTED])}>
                <Spinner size={SpinnerSize.MEDIUM}/>
            </div>);
    }
    // render datagrid
    render() {
        const { className, style, rowType, footer, dataqa, isLoading } = this.props;
        const isDataLoading = isLoading !== undefined ? isLoading : this.state.isLoading;
        const isDetailPaneOpen = this.isDetailPaneOpen();
        const isCompactDatagrid = (rowType && rowType === GridRowType.COMPACT) || rowType === GridRowType.COMPACT_ROWS_WITH_DETAIL_PANE
            ? true
            : false;
        return (<div className={classNames([
                ClassNames.DATAGRID_HOST,
                isCompactDatagrid && ClassNames.DATAGRID_COMPACT,
                isDetailPaneOpen && ClassNames.DATAGRID_DETAIL_OPEN,
                className,
            ])} style={style} data-qa={dataqa}>
                <div className={ClassNames.DATAGRID_OUTER_WRAPPER}>
                    <div className={ClassNames.DATAGRID_INNER_WRAPPER}>
                        {this.buildDataGridBody()}
                        {footer && footer.showFooter && this.buildDataGridFooter()}
                        {isDataLoading && this.buildDataGridSpinner()}
                    </div>
                    {isDetailPaneOpen && this.buildDetailPaneForRow()}
                </div>
                <div className={ClassNames.DATAGRID_CAL_TABLE}>
                    <div className={ClassNames.DATAGRID_CAL_HEADER}/>
                </div>
            </div>);
    }
}
