Home Reference Source Repository

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.
     */
    static propTypes = {
        columns: PropTypes.array.isRequired,
        items: PropTypes.array.isRequired,
        onSelect: PropTypes.func,
        defaultSortBy: PropTypes.number,
    };

    /**
     * @type {Object}
     */
    state = {
        selected: undefined,
        items: [],
        columns: this.props.columns || [],
        filter: '',
        filterKey: this.props.columns[0].fieldName,
        sortBy: this.props.defaultSortBy || 0,
        desc: 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) });
        }
    }

    /**
     * @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;