/** @flow */ import cn from 'classnames' import FlexColumn from './FlexColumn' import React, { Component, PropTypes } from 'react' import { findDOMNode } from 'react-dom' import shallowCompare from 'react-addons-shallow-compare' import Grid from '../Grid' import SortDirection from './SortDirection' /** * Table component with fixed headers and virtualized rows for improved performance with large data sets. * This component expects explicit width, height, and padding parameters. */ export default class FlexTable extends Component { static propTypes = { 'aria-label': PropTypes.string, /** One or more FlexColumns describing the data displayed in this row */ children: (props, propName, componentName) => { const children = React.Children.toArray(props.children) for (let i = 0; i < children.length; i++) { if (children[i].type !== FlexColumn) { return new Error(`FlexTable only accepts children of type FlexColumn`) } } }, /** Optional CSS class name */ className: PropTypes.string, /** Disable rendering the header at all */ disableHeader: PropTypes.bool, /** Optional CSS class to apply to all column headers */ headerClassName: PropTypes.string, /** Fixed height of header row */ headerHeight: PropTypes.number.isRequired, /** Fixed/available height for out DOM element */ height: PropTypes.number.isRequired, /** Optional renderer to be used in place of table body rows when rowsCount is 0 */ noRowsRenderer: PropTypes.func, /** * Optional callback when a column's header is clicked. * (dataKey: string): void */ onHeaderClick: PropTypes.func, /** * Callback invoked when a user clicks on a table row. * (rowIndex: number): void */ onRowClick: PropTypes.func, /** * Callback invoked with information about the slice of rows that were just rendered. * ({ startIndex, stopIndex }): void */ onRowsRendered: PropTypes.func, /** * Callback invoked whenever the scroll offset changes within the inner scrollable region. * This callback can be used to sync scrolling between lists, tables, or grids. * ({ clientHeight, scrollHeight, scrollTop }): void */ onScroll: PropTypes.func.isRequired, /** * Number of rows to render above/below the visible bounds of the list. * These rows can help for smoother scrolling on touch devices. */ overscanRowsCount: PropTypes.number.isRequired, /** * Optional CSS class to apply to all table rows (including the header row). * This property can be a CSS class name (string) or a function that returns a class name. * If a function is provided its signature should be: (rowIndex: number): string */ rowClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), /** * Callback responsible for returning a data row given an index. * (index: number): any */ rowGetter: PropTypes.func.isRequired, /** * Either a fixed row height (number) or a function that returns the height of a row given its index. * (index: number): number */ rowHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]).isRequired, /** Number of rows in table. */ rowsCount: PropTypes.number.isRequired, /** Row index to ensure visible (by forcefully scrolling if necessary) */ scrollToIndex: PropTypes.number, /** Vertical offset. */ scrollTop: PropTypes.number, /** * Sort function to be called if a sortable header is clicked. * (dataKey: string, sortDirection: SortDirection): void */ sort: PropTypes.func, /** FlexTable data is currently sorted by this :dataKey (if it is sorted at all) */ sortBy: PropTypes.string, /** FlexTable data is currently sorted in this direction (if it is sorted at all) */ sortDirection: PropTypes.oneOf([SortDirection.ASC, SortDirection.DESC]), /** Width of list */ width: PropTypes.number.isRequired } static defaultProps = { disableHeader: false, headerHeight: 0, noRowsRenderer: () => null, onRowsRendered: () => null, onScroll: () => null, overscanRowsCount: 10 } constructor (props) { super(props) this.state = { scrollbarWidth: 0 } this._createRow = this._createRow.bind(this) } /** * See Grid#recomputeGridSize */ recomputeRowHeights () { this.refs.Grid.recomputeGridSize() } componentDidMount () { this._setScrollbarWidth() } componentDidUpdate () { this._setScrollbarWidth() } render () { const { className, disableHeader, headerHeight, height, noRowsRenderer, onRowsRendered, onScroll, overscanRowsCount, rowClassName, rowHeight, rowsCount, scrollToIndex, scrollTop, width } = this.props const { scrollbarWidth } = this.state const availableRowsHeight = height - headerHeight // This row-renderer wrapper function is necessary in order to trigger re-render when the // sort-by or sort-direction have changed (else Grid will not see any props changes) const rowRenderer = index => { return this._createRow(index) } const rowClass = rowClassName instanceof Function ? rowClassName(-1) : rowClassName return (