mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
[sqllab] table refactor (#2587)
* make react-virtualized table work use dynamic sizing for cell width enable filtering require height prop for result set component * fix tests and linting * move some state to props * move getTextWidth to visUtils * make striped rows optional * fix striped proptype * update name to FilterableTable * add basic test and fix linting * accept array of columns keys rather than an array of objects that needs to be mapped * move container div inside the component * rename styles * fit table component to width if it's smaller than parent container * move stylesheet to javascript folder otherwise it throws an error on npm run prod * move css to index.jsx * fix result set spec * fix linting and test * fix result set props * keep list immutable
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
import { List } from 'immutable';
|
||||
import React, { PropTypes, PureComponent } from 'react';
|
||||
import {
|
||||
Column,
|
||||
Table,
|
||||
SortDirection,
|
||||
SortIndicator,
|
||||
} from 'react-virtualized';
|
||||
import { getTextWidth } from '../../modules/visUtils';
|
||||
|
||||
const propTypes = {
|
||||
orderedColumnKeys: PropTypes.array.isRequired,
|
||||
data: PropTypes.array.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
filterText: PropTypes.string,
|
||||
headerHeight: PropTypes.number,
|
||||
overscanRowCount: PropTypes.number,
|
||||
rowHeight: PropTypes.number,
|
||||
striped: PropTypes.bool,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
filterText: '',
|
||||
headerHeight: 32,
|
||||
overscanRowCount: 10,
|
||||
rowHeight: 32,
|
||||
striped: true,
|
||||
};
|
||||
|
||||
export default class FilterableTable extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.list = List(this.formatTableData(props.data));
|
||||
this.headerRenderer = this.headerRenderer.bind(this);
|
||||
this.rowClassName = this.rowClassName.bind(this);
|
||||
this.sort = this.sort.bind(this);
|
||||
|
||||
this.widthsForColumnsByKey = this.getWidthsForColumns();
|
||||
this.totalTableWidth = props.orderedColumnKeys
|
||||
.map(key => this.widthsForColumnsByKey[key])
|
||||
.reduce((curr, next) => curr + next);
|
||||
|
||||
this.state = {
|
||||
sortBy: props.orderedColumnKeys[0],
|
||||
sortDirection: SortDirection.ASC,
|
||||
fitted: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fitTableToWidthIfNeeded();
|
||||
}
|
||||
|
||||
getDatum(list, index) {
|
||||
return list.get(index % list.size);
|
||||
}
|
||||
|
||||
getWidthsForColumns() {
|
||||
const PADDING = 40; // accounts for cell padding and width of sorting icon
|
||||
const widthsByColumnKey = {};
|
||||
this.props.orderedColumnKeys.forEach((key) => {
|
||||
const colWidths = this.list
|
||||
.map(d => getTextWidth(d[key]) + PADDING) // get width for each value for a key
|
||||
.push(getTextWidth(key) + PADDING); // add width of column key to end of list
|
||||
// set max width as value for key
|
||||
widthsByColumnKey[key] = Math.max(...colWidths);
|
||||
});
|
||||
return widthsByColumnKey;
|
||||
}
|
||||
|
||||
fitTableToWidthIfNeeded() {
|
||||
const containerWidth = this.container.getBoundingClientRect().width;
|
||||
if (containerWidth > this.totalTableWidth) {
|
||||
this.totalTableWidth = containerWidth - 2; // accomodates 1px border on container
|
||||
}
|
||||
this.setState({ fitted: true });
|
||||
}
|
||||
|
||||
formatTableData(data) {
|
||||
const formattedData = data.map((row) => {
|
||||
const newRow = {};
|
||||
for (const k in row) {
|
||||
const val = row[k];
|
||||
if (typeof (val) === 'string') {
|
||||
newRow[k] = val;
|
||||
} else {
|
||||
newRow[k] = JSON.stringify(val);
|
||||
}
|
||||
}
|
||||
return newRow;
|
||||
});
|
||||
return formattedData;
|
||||
}
|
||||
|
||||
hasMatch(text, row) {
|
||||
const values = [];
|
||||
for (const key in row) {
|
||||
if (row.hasOwnProperty(key)) {
|
||||
values.push(row[key].toLowerCase());
|
||||
}
|
||||
}
|
||||
return values.some(v => v.includes(text.toLowerCase()));
|
||||
}
|
||||
|
||||
headerRenderer({ dataKey, label, sortBy, sortDirection }) {
|
||||
return (
|
||||
<div>
|
||||
{label}
|
||||
{sortBy === dataKey &&
|
||||
<SortIndicator sortDirection={sortDirection} />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
rowClassName({ index }) {
|
||||
let className = '';
|
||||
if (this.props.striped) {
|
||||
className = index % 2 === 0 ? 'even-row' : 'odd-row';
|
||||
}
|
||||
return className;
|
||||
}
|
||||
|
||||
sort({ sortBy, sortDirection }) {
|
||||
this.setState({ sortBy, sortDirection });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { sortBy, sortDirection } = this.state;
|
||||
const {
|
||||
filterText,
|
||||
headerHeight,
|
||||
height,
|
||||
orderedColumnKeys,
|
||||
overscanRowCount,
|
||||
rowHeight,
|
||||
} = this.props;
|
||||
|
||||
let sortedAndFilteredList = this.list;
|
||||
// filter list
|
||||
if (filterText) {
|
||||
sortedAndFilteredList = this.list.filter(row => this.hasMatch(filterText, row));
|
||||
}
|
||||
// sort list
|
||||
sortedAndFilteredList = sortedAndFilteredList
|
||||
.sortBy(item => item[sortBy])
|
||||
.update(list => sortDirection === SortDirection.DESC ? list.reverse() : list);
|
||||
|
||||
const rowGetter = ({ index }) => this.getDatum(sortedAndFilteredList, index);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ height }}
|
||||
className="filterable-table-container"
|
||||
ref={(ref) => { this.container = ref; }}
|
||||
>
|
||||
{this.state.fitted &&
|
||||
<Table
|
||||
ref="Table"
|
||||
headerHeight={headerHeight}
|
||||
height={height - 2}
|
||||
overscanRowCount={overscanRowCount}
|
||||
rowClassName={this.rowClassName}
|
||||
rowHeight={rowHeight}
|
||||
rowGetter={rowGetter}
|
||||
rowCount={sortedAndFilteredList.size}
|
||||
sort={this.sort}
|
||||
sortBy={sortBy}
|
||||
sortDirection={sortDirection}
|
||||
width={this.totalTableWidth}
|
||||
>
|
||||
{orderedColumnKeys.map(columnKey => (
|
||||
<Column
|
||||
dataKey={columnKey}
|
||||
disableSort={false}
|
||||
headerRenderer={this.headerRenderer}
|
||||
width={this.widthsForColumnsByKey[columnKey]}
|
||||
label={columnKey}
|
||||
key={columnKey}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FilterableTable.propTypes = propTypes;
|
||||
FilterableTable.defaultProps = defaultProps;
|
||||
@@ -0,0 +1,60 @@
|
||||
.ReactVirtualized__Table__headerRow {
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.ReactVirtualized__Table__row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.ReactVirtualized__Table__headerTruncatedText {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ReactVirtualized__Table__headerColumn,
|
||||
.ReactVirtualized__Table__rowColumn {
|
||||
min-width: 0px;
|
||||
border-right: 1px solid #ccc;
|
||||
align-self: center;
|
||||
padding: 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.ReactVirtualized__Table__headerColumn:last-of-type,
|
||||
.ReactVirtualized__Table__rowColumn:last-of-type {
|
||||
border-right: 0px;
|
||||
}
|
||||
.ReactVirtualized__Table__headerColumn:focus,
|
||||
.ReactVirtualized__Table__Grid:focus {
|
||||
outline: none;
|
||||
}
|
||||
.ReactVirtualized__Table__rowColumn {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ReactVirtualized__Table__sortableHeaderColumn {
|
||||
cursor: pointer;
|
||||
}
|
||||
.ReactVirtualized__Table__sortableHeaderIconContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.ReactVirtualized__Table__sortableHeaderIcon {
|
||||
flex: 0 0 24px;
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
fill: currentColor;
|
||||
}
|
||||
.even-row { background: #f2f2f2; }
|
||||
.odd-row { background: #ffffff; }
|
||||
.even-row,
|
||||
.odd-row {
|
||||
border: none;
|
||||
}
|
||||
.filterable-table-container {
|
||||
overflow: auto;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
Reference in New Issue
Block a user