src/material/FilterTable.js
import React from 'react';
import PropTypes from 'prop-types';
import deepeq from 'deep-equal';
import {
Table,
TableBody,
TableHeader,
TableHeaderColumn,
TableRow,
TableRowColumn,
} from 'material-ui/Table';
import Toggle from 'material-ui/Toggle';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import TextField from 'material-ui/TextField';
/**
* @desc
* A material-ui [Table](http://www.material-ui.com/#/components/table) with sorting and filtering.
* ```
* const columns = [
* {key: 'id', fieldName: 'Id', name: 'Id'},
* {key: 'title', fieldName: 'Title', name: 'Tittel'},
* ]
*
* const items = [
* {Id: 23, Title: 'This one time...'},
* {Id: 7, Title: 'Free lunch'},
* ]
*
*
* <FilterTable
* columns={columns}
* items={items}
* defaultSortBy={1}
* onSelect={this.handleSelect}
* />
* ```
*/
class FilterTable extends React.Component {
/**
* @type {Object}
* @property {[{key: string, fieldName: string, name: string}]} columns list of columns.
* @property {Object[]} items list of items. {<fieldName>: <value>}
* @property {function(selectedItem: Object):} onSelect callback function for selected item.
* @property {number} defaultSortBy which column index to default sort by. Default: 0.
* @property {boolean} defaultDesc is descending ordering on by default?
*/
static propTypes = {
columns: PropTypes.array.isRequired,
items: PropTypes.array.isRequired,
onSelect: PropTypes.func,
defaultSortBy: PropTypes.number,
defaultDesc: PropTypes.bool,
};
/**
* @type {Object}
*/
state = {
selected: undefined,
items: [],
columns: this.props.columns || [],
filter: '',
filterKey: this.props.columns[0].fieldName,
sortBy: this.props.defaultSortBy || 0,
desc: this.props.defaultDesc || false,
}
componentDidMount() {
this.setState({
items: this.filterAndSort(this.props.items),
});
}
componentWillReceiveProps(nextProps) {
if (!deepeq(this.props.items, nextProps.items)) {
this.setState({
items: this.filterAndSort(nextProps.items),
selected: undefined,
});
}
}
/**
* @type {function}
* @param {Object[]} items
* @param {Object} options
* @param {string} options.filter
* @param {string} options.filterKey
* @param {number} options.sortBy
* @param {boolean} options.desc
* @return {Object[]} list of filtered and sorted items.
*/
filterAndSort = (items, options) => {
items = items || [];
const opts = Object.assign({
filter: this.state.filter,
filterKey: this.state.filterKey,
sortBy: this.state.sortBy,
desc: this.state.desc,
}, options);
const { columns } = this.state;
const { filter, filterKey, sortBy, desc } = opts;
const sortKey = columns[sortBy].fieldName;
let result = items.map((item) => { return {...item}; });
if (filter && filter.length > 2) {
result = result.filter((item) => {
return item[filterKey].toLowerCase().indexOf(filter.toLowerCase()) !== -1;
});
}
result.sort((a,b) => {
const x = typeof a[sortKey] === "string" ? a[sortKey].toLowerCase() : a[sortKey];
const y = typeof b[sortKey] === "string" ? b[sortKey].toLowerCase() : b[sortKey];
if (desc) {
return x < y ? 1 : -1;
}
return x < y ? -1 : 1;
});
return result;
}
/**
* @type {function}
*/
selectionCallback = (item) => {
if (this.props.onSelect) {
this.props.onSelect(item);
}
}
/**
* @type {function}
*/
handleRowSelection = (selected) => {
this.setState({selected: selected[0]});
this.selectionCallback(this.state.items[selected[0]]);
}
/**
* @type {function}
*/
handleFilterChange = (e) => {
this.setState({
filter: e.target.value,
selected: null,
items: this.filterAndSort(this.props.items, {filter: e.target.value}),
});
this.selectionCallback(undefined);
}
/**
* @type {function}
*/
handleSortByChange = (e, index, value) => {
this.setState({
sortBy: value,
selected: null,
items: this.filterAndSort(this.props.items, {sortBy: value}),
});
this.selectionCallback(undefined);
}
/**
* @type {function}
*/
handleToggleDesc = (e, checked) => {
this.setState({
desc: checked,
selected: null,
items: this.filterAndSort(this.props.items, {desc: checked}),
});
this.selectionCallback(undefined);
}
/**
* @return {Node}
*/
render() {
const { columns, items, selected, desc } = this.state;
return (
<div>
<div style={{display: 'flex', flexWrap: 'wrap'}}>
<TextField
style={{marginRight: '1em'}}
floatingLabelText="Filtrer på"
hintText="Minst 3 tegn"
value={this.state.filter}
onChange={this.handleFilterChange}
/>
<SelectField
floatingLabelText="Sorter på"
value={this.state.sortBy}
onChange={this.handleSortByChange}
>
{columns.map((col, index) => (
<MenuItem
key={index}
value={index}
primaryText={col.name}
/>
))}
</SelectField>
<Toggle
style={{display: 'inline-block', width: 'auto', marginTop: 35}}
label="Reverser sortering"
labelStyle={{whiteSpace: 'nowrap'}}
toggled={desc}
onToggle={this.handleToggleDesc}
/>
</div>
<Table
fixedHeader={true}
selectable={true}
multiSelectable={false}
onRowSelection={this.handleRowSelection}
>
<TableHeader
displaySelectAll={false}
adjustForCheckbox={true}
enableSelectAll={false}
>
<TableRow>
{columns.map((col) => (
<TableHeaderColumn key={col.key}>{col.name}</TableHeaderColumn>
))}
</TableRow>
</TableHeader>
<TableBody
displayRowCheckbox={true}
deselectOnClickaway={false}
>
{items.map((prop, index) => (
<TableRow
key={index}
selected={selected === index}
>
{columns.map((col) => (
<TableRowColumn key={col.key}>{prop[col.fieldName]}</TableRowColumn>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
);
}
}
export default FilterTable;