@bigfishtv/cockpit
Version:
201 lines (186 loc) • 6.38 kB
JavaScript
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { Table, Column } from 'fixed-data-table'
import deepEqual from 'deep-equal'
import classnames from 'classnames'
import { titleCase } from '../../utils/stringUtils'
import { sortByObjectKey } from '../../utils/tableUtils'
import * as SortTypes from '../../constants/SortTypes'
import FixedDataTableTextCell from './cell/FixedDataTableTextCell'
import FixedDataTableHeaderCellSort from './cell/FixedDataTableHeaderCellSort'
// we define this because react-docgen fails when defaultProp directly references an imported component
const DefaultHeaderCell = props => <FixedDataTableHeaderCellSort {...props} />
const DefaultCell = props => <FixedDataTableTextCell {...props} />
/**
* Typically wrapped by Table component, simply displays a fixed-data-table with the data provided
*/
export default class FixedDataTable extends Component {
static propTypes = {
/** Array of objects representing table rows */
data: PropTypes.arrayOf(PropTypes.object),
/** Array of objects representing table columns/schema */
fields: PropTypes.arrayOf(PropTypes.object),
/** Array of selected row ids */
selectedIds: PropTypes.arrayOf(PropTypes.number),
/** Whether or not component should control its own sorting */
uncontrolled: PropTypes.bool,
/** Whether or not to inject a checkbox column to control selection state */
checkboxSelection: PropTypes.bool,
tableWidth: PropTypes.number,
tableHeight: PropTypes.number,
cellWidth: PropTypes.number,
rowHeight: PropTypes.number,
headerHeight: PropTypes.number,
/** column key to sort by, can even be nested e.g. 'collection.title' */
defaultSortField: PropTypes.string,
defaultSortDirections: PropTypes.oneOf([SortTypes.ASC, SortTypes.DESC]),
/** On row select */
onSelect: PropTypes.func,
/** On row double click */
onSelected: PropTypes.func,
/** For updating selectedIds in bulk */
onSelectionChange: PropTypes.func,
/** Component to replace default HeaderCell */
HeaderCell: PropTypes.func,
/** Component to replace default table cell component */
DefaultCell: PropTypes.func,
}
static defaultProps = {
data: [],
fields: [],
selectedIds: [],
uncontrolled: true,
tableWidth: 600,
tableHeight: 500,
cellWidth: 250,
rowHeight: 50,
headerHeight: 36,
defaultSortField: 'modified',
defaultSortDirection: SortTypes.DESC,
onSelect: () => console.warn('[FixedDataTable] no onSelect prop provided'),
onSelected: () => console.warn('[FixedDataTable] no onSelected prop provided'),
DefaultHeaderCell,
DefaultCell,
}
constructor(props) {
super()
const columnWidths = {}
props.fields.map(field => {
columnWidths[field.key] = field.width || props.cellWidth
})
this.state = {
data: props.data,
columnSortDirections: props.columnKey && props.sortDirection ? { [props.columnKey]: props.sortDirection } : {},
columnWidths,
}
}
componentWillReceiveProps(nextProps) {
if (!deepEqual(this.state.data, nextProps.data)) {
const columnKey = !this.state.data.length
? this.props.defaultSortField
: Object.keys(this.state.columnSortDirections)[0]
const sortDirection = !this.state.data.length
? this.props.defaultSortDirection
: this.state.columnSortDirections[columnKey]
this.setState({ data: nextProps.data }, () => {
if (this.props.uncontrolled) this.onSortChange(columnKey, sortDirection)
})
}
}
onSortChange = (columnKey, sortDirection) => {
const { fields, uncontrolled, onSortChange } = this.props
const field = fields.filter(field => field.key === columnKey)[0]
const sortType = 'sortType' in field ? field.sortType : 'string'
if (!uncontrolled) {
onSortChange(columnKey, sortDirection, sortType)
} else {
const data = [...this.state.data].sort(sortByObjectKey(columnKey, sortDirection, sortType))
this.setState({ data })
}
this.setState({ columnSortDirections: { [columnKey]: sortDirection } })
}
handleColumnResize = (newColumnWidth, columnKey) => {
this.setState({
columnWidths: {
...this.state.columnWidths,
[columnKey]: newColumnWidth,
},
})
}
handleGetRowClassName = rowIndex => {
const row = this.state.data[rowIndex]
const selected = this.props.selectedIds.indexOf(row.id) >= 0
return selected ? 'selected' : ''
}
handleRowClick = (event, rowIndex) => {
event.stopPropagation()
this.props.onSelect(this.state.data[rowIndex])
}
handleRowDoubleClick = (event, rowIndex) => {
event.stopPropagation()
this.props.onSelected(this.state.data[rowIndex])
}
render() {
const { data, columnSortDirections, columnWidths } = this.state
const {
fields,
selectedIds,
checkboxSelection,
tableWidth,
tableHeight,
headerHeight,
rowHeight,
HeaderCell,
DefaultCell,
DefaultHeaderCell,
...props
} = this.props
return (
<Table
{...props}
rowsCount={data.length}
headerHeight={headerHeight}
rowHeight={rowHeight}
width={tableWidth}
height={tableHeight}
onColumnResizeEndCallback={this.handleColumnResize}
isColumnResizing={false}
rowClassNameGetter={this.handleGetRowClassName}
onRowClick={checkboxSelection ? () => {} : this.handleRowClick}
onRowDoubleClick={this.handleRowDoubleClick}>
{fields.map((field, i) => {
const BodyCell = field.Cell || DefaultCell
const HeaderCell = field.HeaderCell || DefaultHeaderCell
const width = columnWidths[field.key]
const headerTitle = field.value || titleCase(field.key)
const sortDir = columnSortDirections[field.key]
const schema = field.schema || {}
return (
<Column
key={i}
columnKey={field.key}
fixed={field.fixed}
isResizable={field.resizable}
minWidth={field.minWidth}
maxWidth={field.maxWidth}
flexGrow={field.flexGrow}
header={
<HeaderCell
data={data}
selectedIds={selectedIds}
onSelectionChange={this.props.onSelectionChange}
onSortChange={this.onSortChange}
sortDir={sortDir}
className={classnames(field.sortable && 'sortable')}>
{headerTitle}
</HeaderCell>
}
cell={<BodyCell data={data} schema={schema} onSelect={this.props.onSelect} selectedIds={selectedIds} />}
width={width}
/>
)
})}
</Table>
)
}
}