From 0558d0b96c81f9b28e1bb7841fb5e953d4e02008 Mon Sep 17 00:00:00 2001 From: Esko Ikkala <esko.ikkala@aalto.fi> Date: Fri, 15 Jun 2018 16:19:08 +0300 Subject: [PATCH] Render results with react-virtualized --- src/client/components/VirtualizedTable.js | 232 ++++++++++++++++++++++ src/client/containers/MapApp.js | 11 +- src/client/index.js | 4 +- src/client/styles/virtualized-table.css | 34 ++++ 4 files changed, 278 insertions(+), 3 deletions(-) create mode 100644 src/client/components/VirtualizedTable.js create mode 100644 src/client/styles/virtualized-table.css diff --git a/src/client/components/VirtualizedTable.js b/src/client/components/VirtualizedTable.js new file mode 100644 index 00000000..293fc288 --- /dev/null +++ b/src/client/components/VirtualizedTable.js @@ -0,0 +1,232 @@ +import React from 'react'; +import Immutable from 'immutable'; +import PropTypes from 'prop-types'; +import { + AutoSizer, + Column, + Table, + SortDirection, + SortIndicator +} from 'react-virtualized'; +import { withStyles } from '@material-ui/core/styles'; + +const styles = theme => ({ + root: { + width: '100%', + marginTop: theme.spacing.unit * 25, + }, + headerRow: { + borderBottom: '1px solid #e0e0e0' + }, + evenRow: { + borderBottom: '1px solid #e0e0e0' + }, + oddRow: { + borderBottom: '1px solid #e0e0e0', + //backgroundColor: '#fafafa' + }, + headerColumn: { + textTransform: 'none' + }, + noRows: { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontSize: '1em', + color: '#bdbdbd', + } + +}); + +class VirtualizedTable extends React.PureComponent { + + constructor(props) { + super(props); + + const sortBy = 'source'; + const sortDirection = SortDirection.ASC; + const sortedList = this._sortList({sortBy, sortDirection}); + + + this.state = { + disableHeader: false, + headerHeight: 30, + height: 500, + overscanRowCount: 10, + rowHeight: 40, + rowCount: this.props.list.size, + scrollToIndex: undefined, + sortBy, + sortDirection, + sortedList, + useDynamicRowHeight: false, + }; + + this._getRowHeight = this._getRowHeight.bind(this); + this._headerRenderer = this._headerRenderer.bind(this); + this._noRowsRenderer = this._noRowsRenderer.bind(this); + this._onRowCountChange = this._onRowCountChange.bind(this); + this._onScrollToRowChange = this._onScrollToRowChange.bind(this); + this._rowClassName = this._rowClassName.bind(this); + this._sort = this._sort.bind(this); + } + + render() { + const { + disableHeader, + headerHeight, + height, + overscanRowCount, + rowHeight, + rowCount, + scrollToIndex, + sortBy, + sortDirection, + sortedList, + useDynamicRowHeight, + } = this.state; + + const { classes } = this.props; + + const rowGetter = ({index}) => this._getDatum(sortedList, index); + + return ( + <div> + <AutoSizer> + {({width}) => ( + <Table + disableHeader={disableHeader} + headerClassName={classes.headerColumn} + headerHeight={headerHeight} + height={height} + noRowsRenderer={this._noRowsRenderer} + overscanRowCount={overscanRowCount} + rowClassName={this._rowClassName} + rowHeight={useDynamicRowHeight ? this._getRowHeight : rowHeight} + rowGetter={rowGetter} + rowCount={rowCount} + scrollToIndex={scrollToIndex} + sort={this._sort} + sortBy={sortBy} + sortDirection={sortDirection} + width={width} + style={classes} + > + <Column + label="Label" + cellDataGetter={({rowData}) => rowData.label} + dataKey="label" + width={150} + /> + <Column + label="Type" + cellDataGetter={({rowData}) => rowData.typeLabel} + dataKey="typeLabel" + width={150} + /> + <Column + label="Area" + cellDataGetter={({rowData}) => rowData.broaderAreaLabel} + dataKey="broaderAreaLabel" + width={150} + /> + <Column + label="Source" + cellDataGetter={({rowData}) => rowData.source} + dataKey="source" + width={150} + /> + </Table> + )} + </AutoSizer> + </div> + ); + } + + _getDatum(list, index) { + return list.get(index % list.size); + } + + _getRowHeight({index}) { + const list = this.props.list; + + return this._getDatum(list, index).size; + } + + _headerRenderer({dataKey, sortBy, sortDirection}) { + return ( + <div> + Full Name + {sortBy === dataKey && <SortIndicator sortDirection={sortDirection} />} + </div> + ); + } + + _noRowsRenderer() { + const { classes } = this.props; + return <div className={classes.noRows}>No rows</div>; + } + + _onRowCountChange(event) { + const rowCount = parseInt(event.target.value, 10) || 0; + + this.setState({rowCount}); + } + + _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}); + } + + _rowClassName({index}) { + const { classes } = this.props; + if (index < 0) { + return classes.headerRow; + } else { + return index % 2 === 0 ? classes.evenRow : classes.oddRow; + } + } + + _sort({sortBy, sortDirection}) { + const sortedList = this._sortList({sortBy, sortDirection}); + + this.setState({sortBy, sortDirection, sortedList}); + } + + _sortList({sortBy, sortDirection}) { + const list = this.props.list; + + return list + .sortBy(item => item[sortBy]) + .update( + list => (sortDirection === SortDirection.DESC ? list.reverse() : list), + ); + } + + _updateUseDynamicRowHeight(value) { + this.setState({ + useDynamicRowHeight: value, + }); + } +} + +VirtualizedTable.propTypes = { + list: PropTypes.instanceOf(Immutable.List).isRequired, + classes: PropTypes.object.isRequired, +}; + +export default withStyles(styles)(VirtualizedTable); diff --git a/src/client/containers/MapApp.js b/src/client/containers/MapApp.js index f107a062..9fe68e92 100644 --- a/src/client/containers/MapApp.js +++ b/src/client/containers/MapApp.js @@ -17,7 +17,9 @@ import Tab from '@material-ui/core/Tab'; import IntegrationAutosuggest from '../components/IntegrationAutosuggest'; import LeafletMap from '../components/map/LeafletMap'; import Message from '../components/Message'; -import ResultTable from '../components/result-table/ResultTable'; +//import ResultTable from '../components/result-table/ResultTable'; +import VirtualizedTable from '../components/VirtualizedTable'; +import Immutable from 'immutable'; import { @@ -119,6 +121,11 @@ let MapApp = (props) => { const anchor = 'left'; //console.log(props.search.results) + let resultList = []; + if (props.search.results.length > 0) { + resultList = Immutable.List(props.search.results); + } + const drawer = ( <Drawer @@ -156,7 +163,7 @@ let MapApp = (props) => { clearResults={props.clearResults} /> {props.search.results.length > 0 && - <ResultTable data={props.search.results} /> + <VirtualizedTable list={resultList} /> } </Drawer> ); diff --git a/src/client/index.js b/src/client/index.js index fe9e0301..3bd1f2de 100644 --- a/src/client/index.js +++ b/src/client/index.js @@ -6,11 +6,13 @@ import { Provider } from 'react-redux'; import reducer from './reducers'; import rootEpic from './epics'; import ReduxToastr from 'react-redux-toastr'; -import 'react-redux-toastr/lib/css/react-redux-toastr.min.css'; import { bindActionCreators } from 'redux'; import { actions as toastrActions } from 'react-redux-toastr'; import App from './components/App'; +import 'react-redux-toastr/lib/css/react-redux-toastr.min.css'; +import 'react-virtualized/styles.css'; + const store = createStore( reducer, // applyMiddleware() tells createStore() how to handle middleware diff --git a/src/client/styles/virtualized-table.css b/src/client/styles/virtualized-table.css new file mode 100644 index 00000000..0819af2c --- /dev/null +++ b/src/client/styles/virtualized-table.css @@ -0,0 +1,34 @@ +.Table { + width: 100%; + margin-top: 15px; +} +.headerRow, +.evenRow, +.oddRow { + border-bottom: 1px solid #e0e0e0; +} +.oddRow { + background-color: #fafafa; +} +.headerColumn { + text-transform: none; +} +.exampleColumn { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + + +.noRows { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 1em; + color: #bdbdbd; +} -- GitLab