labo-components
Version:
219 lines (191 loc) • 7.42 kB
JSX
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import IDUtil from '../../util/IDUtil';
import BulkActions from './helpers/BulkActions';
import Pagination from './helpers/Pagination';
/**
* A Table component with headers and rows that handles row selection and pagination.
* Its contents are provided by the props
* TODO move this component to a higher package, since it might be reusable in other places as well
*/
class SortTable extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
currentPage: this.props.currentPage,
bulkAction: null,
items: props.items,
selection: this.props.selection ? this.props.selection : [],
sort: this.props.defaultSort
};
}
//update the state when receiving new props
componentWillReceiveProps(nextProps) {
if (nextProps.items !== this.state.items) {
if(this.state.items.length > 0) //wipe the selection if items have changed
{
this.setState({
items: this.props.onSort(nextProps.items, this.state.sort),
selection: []
});
}
else //if items are loaded for the first time, use the selection from the properties if present
{
this.setState({
items: this.props.onSort(nextProps.items, this.state.sort),
selection: this.props.selection ? this.props.selection : []
});
}
}
}
componentDidUpdate(prevProps, prevState) {
if(prevState.selection !== this.state.selection && this.props.onSelectQuery) {
this.props.onSelectQuery(this.state.selection);
}
}
deselectAll = () => {
if(!this.props.keepSelectionWhenDone) {
this.setState({selection : []});
}
}
//sort data based on the given field
sort(field) {
const sort = {
field: field,
order: this.state.sort.field === field && this.state.sort.order === 'asc' ? 'desc' : 'asc'
};
this.setState({
sort,
items: this.props.onSort(this.props.items, sort)
});
}
selectAll(e) {
this.setState({
selection: e.target.checked ? this.state.items.slice() : []
});
}
selectItem(item, e) {
//rewrite the hard to read ?/: stuff
this.setState({
selection: e.target.checked ? // add if not in the array yet
this.state.selection.includes(item) ? this.state.selection
: [...this.state.selection, item]
: // remove
this.state.selection.filter(selected => selected !== item)
});
}
//go to a different page in the table
setPage(currentPage) {
this.setState({ currentPage });
}
/*********************************************************************
***************************** RENDERING FUNCTIONS ********************
*********************************************************************/
//get a header <th> element
getHeader(index, field, content, sortable) {
const active = sortable && this.state.sort.field === field;
const sortFunc = sortable ? { onClick: this.sort.bind(this, field) } : {};
return (
<th
key={index}
className={classNames({sortable, active, desc: active && this.state.sort.order === 'desc'})}
{...sortFunc}
>
{content}
</th>
);
}
render() {
// pagination vars
const pageCount = Math.ceil(this.state.items.length / this.props.perPage);
const currentPage = Math.min(this.state.currentPage, pageCount - 1);
const currentIndex = currentPage * this.props.perPage;
const itemsOnPage = this.state.items.slice(
currentIndex,
currentIndex + this.props.perPage
);
//populate the header using the provided header function
const header = this.props.head.map((head, index) =>
this.getHeader(index, head.field, head.content, head.sortable)
);
//populate the table body using the provided row function
const tableBody = itemsOnPage.map((item, index) => (
<tr key={index}>
<td>
<input
type="checkbox"
checked={this.state.selection.includes(item)}
onChange={this.selectItem.bind(this, item)}/>
</td>
{this.props.row(item).map((td, index) => (
<td key={index} {...td.props}>
{td.content}
</td>
))}
</tr>
));
//show the correct message when there are no items
let message = null;
if(this.state.items.length === 0) {
if(this.state.loading) {
message = <h3 className="error">Loading...</h3>
} else {
message = <h3 className="error">No results</h3>
}
}
return (
<div className={IDUtil.cssClassName('sort-table')}>
<table>
<thead>
<tr>
<th>
<input
type="checkbox"
title="Select all"
checked={
this.state.items.length > 0 &&
this.state.selection.length === this.state.items.length
}
onChange={this.selectAll.bind(this)}/>
</th>
{header}
</tr>
</thead>
<tbody className={this.props.loading ? 'loading' : ''}>
{tableBody}
</tbody>
</table>
{message}
<Pagination
currentPage={currentPage}
perPage={this.props.perPage}
pageCount={pageCount}
onClick={this.setPage.bind(this)}/>
<BulkActions
bulkActions={this.props.bulkActions}
selection={this.state.selection}
onActionDone={this.deselectAll}
/>
</div>
)
}
}
SortTable.propTypes = {
items: PropTypes.array.isRequired,
bulkActions: PropTypes.array.isRequired,
head: PropTypes.array.isRequired, //function for generating a header row
row: PropTypes.func.isRequired, //function for generating a row of data
onSort: PropTypes.func.isRequired,
perPage: PropTypes.number,
currentPage: PropTypes.number,
defaultSort: PropTypes.object.isRequired,
selection: PropTypes.array, //already selected items
keepSelectionWhenDone: PropTypes.bool //whether selection should be kept after action is done
};
SortTable.defaultProps = {
perPage: 20,
currentPage: 0,
defaultSort: { field: null, order: 'asc' },
};
export default SortTable;