Skip to content
Snippets Groups Projects
VirtualizedTable.js 8.28 KiB
import React from 'react';
import Immutable from 'immutable';
import PropTypes from 'prop-types';
import Grid from '@material-ui/core/Grid';
import { withStyles } from '@material-ui/core/styles';
import SearchField from '../components/SearchField';
import ResultFilterDialogSingle from './ResultFilterDialogSingle';
import {
  AutoSizer,
  Column,
  Table,
  SortIndicator
} from 'react-virtualized';

// https://github.com/bvaughn/react-virtualized/issues/650
// https://github.com/bvaughn/react-virtualized/blob/master/docs/usingAutoSizer.md

const styles = () => ({
  root: {
    display: 'flex',
    height: '100%',
    flexGrow: 1,
  },
  container: {
    height: '100%',
    width: '100%',
    flexDirection: 'column'
  },
  resultsInfo: {
    flexGrow: 0
  },
  searchField: {
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    height: 70
  },
});

const tableStyles = {
  tableRoot: {
    fontFamily: 'Roboto',
  },
  headerRow: {
    textTransform: 'none',
    borderBottom: '1px solid rgba(224, 224, 224, 1)'
  },
  evenRow: {
    borderBottom: '1px solid rgba(224, 224, 224, 1)',
    //backgroundColor: '#fafafa'
  },
  oddRow: {
    borderBottom: '1px solid rgba(224, 224, 224, 1)',
  },
  noRows: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    fontSize: '1em',
    color: '#bdbdbd',
  }
};

const columnWidth = 115;
const calculateRowStyle = ({ index }) => {
  if (index < 0) {
    return tableStyles.headerRow;
  } else {
    return index % 2 === 0 ? tableStyles.evenRow : tableStyles.oddRow;
  }
};

class VirtualizedTable extends React.PureComponent {

  constructor(props) {
    super(props);
    this._noRowsRenderer = this._noRowsRenderer.bind(this);
    this._sort = this._sort.bind(this);
  }

  render() {
    const { classes, list } = this.props;
    const rowGetter = ({index}) => this._getDatum(list, index);

    const headerRenderer = ({
      dataKey,
      label,
      sortBy,
      sortDirection,
    }) => {
      const showSortIndicator = sortBy === dataKey;
      const children = [
        <span
          className="ReactVirtualized__Table__headerTruncatedText"
          style={showSortIndicator ? {} : { marginRight: 16 }}
          key="label"
          title={label}>
          {label}
        </span>,
      ];
      if (showSortIndicator) {
        children.push(
          <SortIndicator key="SortIndicator" sortDirection={sortDirection} />,
        );
      }
      children.push(
        <ResultFilterDialogSingle
          key="resultFilter"
          propertyLabel={label}
          property={dataKey}
          resultValues={this.props.resultValues[dataKey]}
          updateResultsFilter={this.props.updateResultsFilter}
        />
      );
      return children;
    };

    // always render extra columns for now
    const analysisView = true;
    // Some extra columns for analysis view
    let modifier = '';
    let base = '';
    let collector = '';
    let collectionYear = '';
    if (analysisView) {
      modifier = (
        <Column
          label="Modifier"
          cellDataGetter={({rowData}) => rowData.modifier}
          dataKey="modifier"
          headerRenderer={headerRenderer}
          width={columnWidth}
        />
      );
      base = (
        <Column
          label="Base"
          cellDataGetter={({rowData}) => rowData.basicElement}
          dataKey="basicElement"
          headerRenderer={headerRenderer}
          width={columnWidth}
        />
      );
      collector = (
        <Column
          label="Collector"
          cellDataGetter={({rowData}) => rowData.collector}
          dataKey="collector"
          headerRenderer={headerRenderer}
          width={columnWidth}
        />
      );
      collectionYear = (
        <Column
          label="Year"
          cellDataGetter={({rowData}) => rowData.collectionYear}
          dataKey="collectionYear"
          headerRenderer={headerRenderer}
          width={columnWidth}
        />
      );
    }

    const searchField = (
      <SearchField
        search={this.props.search}
        fetchResults={this.props.fetchResults}
        updateQuery={this.props.updateQuery}
        clearResults={this.props.clearResults}
      />
    );

    return (
      <div className={classes.root}>
        <Grid container className={classes.container}>
          <div className={classes.resultsInfo}>
            <div className={classes.searchField}>
              {searchField}
            </div>
          </div>
          {this.props.list.size > 0 &&
            <div style={{ flex: '1 1 auto' }}>
              <AutoSizer>
                {({ height, width }) => (
                  <Table
                    overscanRowCount={10}
                    rowHeight={40}
                    rowGetter={rowGetter}
                    rowCount={this.props.list.size}
                    sort={this._sort}
                    sortBy={this.props.search.sortBy}
                    sortDirection={this.props.search.sortDirection.toUpperCase()}
                    width={width}
                    height={height}
                    headerHeight={50}
                    noRowsRenderer={this._noRowsRenderer}
                    style={tableStyles.tableRoot}
                    rowStyle={calculateRowStyle}
                  >
                    <Column
                      label="Name"
                      cellDataGetter={({rowData}) => rowData.label}
                      dataKey="label"
                      headerRenderer={headerRenderer}
                      width={columnWidth}
                    />
                    {modifier}
                    {base}
                    <Column
                      label="Type"
                      cellDataGetter={({rowData}) => rowData.typeLabel}
                      dataKey="typeLabel"
                      headerRenderer={headerRenderer}
                      width={columnWidth}
                    />
                    <Column
                      label="Area"
                      cellDataGetter={({rowData}) => rowData.broaderAreaLabel}
                      dataKey="broaderAreaLabel"
                      headerRenderer={headerRenderer}
                      width={columnWidth}
                    />
                    {collector}
                    {collectionYear}
                    <Column
                      label="Source"
                      cellDataGetter={({rowData}) => rowData.source}
                      dataKey="source"
                      headerRenderer={headerRenderer}
                      width={columnWidth}
                    />
                  </Table>
                )}
              </AutoSizer>
            </div>
          }
        </Grid>
      </div>
    );
  }

  _getDatum(list, index) {
    return list.get(index % list.size);
  }

  _getRowHeight({index}) {
    const list = this.props.list;
    return this._getDatum(list, index).size;
  }

  _noRowsRenderer() {
    return <div className={tableStyles.noRows}>No rows</div>;
  }

  // _onScrollToRowChange(event) {
  //   const {rowCount} = this.state;
  //   let scrollToIndex = Math.min(
  //     rowCount - 1,
  //     parseInt(event.target.value, 10),
  //   );
  //
  //   if (isNaN(scrollToIndex)) {
  //     scrollToIndex = undefined;
  //   }
  //
  //   this.setState({scrollToIndex});
  // }

  // https://stackoverflow.com/questions/40412114/how-to-do-proper-column-filtering-with-react-virtualized-advice-needed
  _sort({ event, sortBy, sortDirection }) {
    // console.log(event.target)
    if (event.target.id.startsWith('filter') || event.target.className.startsWith('Mui')) {
      event.stopPropagation();
    } else {
      this.props.sortResults({ sortBy, sortDirection: sortDirection.toLowerCase() });
    }
  }
}

VirtualizedTable.propTypes = {
  classes: PropTypes.object.isRequired,
  list: PropTypes.instanceOf(Immutable.List).isRequired,
  search: PropTypes.object.isRequired,
  resultValues: PropTypes.object.isRequired,
  sortResults: PropTypes.func.isRequired,
  updateResultsFilter: PropTypes.func.isRequired,
  updateQuery: PropTypes.func.isRequired,
  fetchSuggestions: PropTypes.func.isRequired,
  clearSuggestions: PropTypes.func.isRequired,
  fetchResults: PropTypes.func.isRequired,
  clearResults: PropTypes.func.isRequired,
};

export default withStyles(styles)(VirtualizedTable);