@6thquake/react-material
Version:
React components that implement Google's Material Design.
820 lines (738 loc) • 20.1 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
/**
* @ignore - do not document.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import update from 'immutability-helper';
import Head from './Head';
import Body from './Body';
import Toolbar from './TableToolbar';
import Pagination from './Pagination';
import filter from '../../utils/filter';
import NoData from '../../NoData';
import { withStyles } from '../../styles';
import { darken, fade, lighten } from '../../styles/colorManipulator';
const styles = theme => ({
root: {
display: 'flex',
flexDirection: 'column',
height: '100%',
width: '100%'
},
spacer: {
flex: 1
},
tbodyRoot: {
position: 'relative',
// height: `calc(100% - 120px)`,
overflowY: 'hidden'
},
main: {
overflowX: 'auto',
width: '100%'
},
left: {
position: 'absolute',
left: 0,
top: 0,
background: '#fff'
},
leftShadow: {
'box-shadow': '6px 0 6px -4px rgba(0,0,0,.2)'
},
right: {
position: 'absolute',
right: 0,
top: 0,
background: '#fff'
},
rightShadow: {
'box-shadow': '-6px 0 6px -4px rgba(0,0,0,.2)'
},
footer: {
display: 'flex',
borderTop: `1px solid
${theme.palette.type === 'light' ? lighten(fade(theme.palette.divider, 1), 0.88) : darken(fade(theme.palette.divider, 1), 0.8)}`
},
footerLeft: {
display: 'flex',
flex: 1,
alignItems: 'center',
borderBottom: `1px solid
${theme.palette.type === 'light' ? lighten(fade(theme.palette.divider, 1), 0.88) : darken(fade(theme.palette.divider, 1), 0.8)}`,
paddingLeft: theme.spacing(2)
},
pagination: {
display: 'flex',
justifyContent: 'flex-end'
}
});
class AwesomeTable extends React.Component {
constructor(_props) {
super(_props);
this.searchedData = {
data: this.props.data
};
this.getSortConfig = props => {
const {
columns
} = props; // this.sortsBy = new Set()
const sorts = [];
columns.map(col => {
if (col.sortable && col.order !== undefined) {
if (col.order === 'asc') {
sorts.push({
index: 0,
key: col.key,
order: col.order,
orderList: ['asc', 'desc', '']
});
}
if (col.order === 'desc') {
sorts.push({
index: 0,
key: col.key,
order: col.order,
orderList: ['desc', 'asc', '']
});
} else {
sorts.push({
index: 0,
key: col.key,
order: col.order,
orderList: ['', 'asc', 'desc']
});
} // this.sortsBy.add(key)
}
});
return {
sorts
};
};
this.sortArray = ['asc', 'desc', ''];
this.tableRefs = {
root: React.createRef('root'),
left: React.createRef('left'),
main: React.createRef('main'),
right: React.createRef('right'),
head: React.createRef('head'),
body: React.createRef('body'),
toolbar: React.createRef('toolbar'),
pagination: React.createRef('pagination')
};
this.dragIndex = {
targetIndex: '',
sourceIndex: ''
};
this.propsCached = {};
this.normalizeColumns = columns => {
const leftColumns = columns.filter(item => item.fixed === 'left');
const rightColumns = columns.filter(item => item.fixed === 'right');
const unfixed = columns.filter(item => !item.fixed);
return [...leftColumns, ...unfixed, ...rightColumns];
};
this.syncTableRowHeight = (isResize, length) => {
const head = this.tableRefs.head.current;
const body = this.tableRefs.body.current;
if (!head || !body) {
return;
}
const len = length || this.state.data.length;
const headDom = ReactDOM.findDOMNode(head);
const headHeight = headDom.getBoundingClientRect().height;
const bodyDom = ReactDOM.findDOMNode(body);
const bodyHeight = bodyDom.getBoundingClientRect().height;
if (isResize) {
// todo : Resizable only axis=x
this.setState({
bodyRowHeight: bodyHeight / len
});
return;
}
console.log('bodyHeight');
this.setState({
bodyHeight,
bodyRowHeight: bodyHeight / len,
headRowHeight: headHeight
});
};
this.handleColDrag = config => {
const {
targetIndex,
sourceIndex
} = config;
const {
columns
} = this.state;
const sourceColumn = columns[sourceIndex];
const targetColumn = columns[targetIndex];
const fixed = targetColumn.fixed;
sourceColumn.fixed = fixed;
this.setState(update(this.state, {
columns: {
$splice: [[sourceIndex, 1], [targetIndex, 0, sourceColumn]]
}
}));
};
this.handleSort = (sort, column) => {
let {
order,
index,
key
} = sort;
const {
OrderProps
} = this.props;
let {
sorts
} = this.state;
const {
onChangeOrder,
multiple
} = OrderProps;
index = (index + 1) % sort.orderList.length;
order = sort.orderList[index];
let flag = true;
if (multiple) {
for (const s of sorts) {
if (s.key === key) {
s.order = order;
s.index = index;
flag = false;
break;
}
}
flag && sorts.push({
key,
order,
index,
orderList: sort.orderList
});
} else {
sorts = [{
key,
order,
index,
orderList: sort.orderList
}];
}
this.setState({
sorts
});
const data = sorts.map(item => {
return {
key: item.key,
order: item.order
};
});
onChangeOrder && onChangeOrder(data);
};
this.handleResize = (index, colomn, size) => {
this.setState(({
columns
}) => {
const nextColumns = [...columns];
nextColumns[index] = _extends({}, nextColumns[index], {
width: size.width
});
return {
columns: nextColumns
};
}, () => {
this.syncTableRowHeight(true);
});
};
this.onSearch = text => {
this.setState({
search: text
}, () => {
this.filteredData();
});
};
this.dragSatrt = index => {
this.dragIndex.sourceIndex = index;
};
this.dragEnd = index => {
// todo : 每次拖拽执行了两次, 多执行了一次。而且返回值不对
// 这里先用类型过滤掉多余的一次
if (typeof index === 'object') {
return;
}
const {
onColDrag
} = this.props;
this.dragIndex.targetIndex = index;
onColDrag && onColDrag(this.dragIndex);
this.handleColDrag(this.dragIndex);
};
this.filteredData = () => {
const {
data,
sync
} = this.props;
const {
search
} = this.state;
let searchData = data;
if (search && sync) {
searchData = filter(data, search);
}
this.searchedData.data = searchData;
const result = this.handlePaginteData();
return result;
};
this.handlePaginteData = () => {
let data = this.searchedData.data;
const {
paginatable,
sync
} = this.props;
if (!sync) {
this.setState({
data
});
return;
}
const {
page,
rowsPerPage
} = this.state;
if (paginatable) {
data = data.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
} else {
return data;
}
this.setState({
data
});
};
this.createCsv = () => {
const head = this.state.columns.reduce((pre, cur) => {
if (cur.render || !cur.title) {
return pre;
}
return `${pre},${cur.title}`;
}, '');
let csv = `${head.slice(1)}\r\n`;
const columns = this.state.columns;
const data = this.searchedData.data;
data.map(entry => {
let row = '';
columns.map(column => {
row += `${entry[column.dataIndex] || ' '},`;
});
csv += `${row}\r\n`;
});
return csv;
};
this.download = (fileName = 'table') => {
const CSV = this.createCsv();
const link = document.createElement('a');
const csvData = new Blob([`\uFEFF${CSV}`], {
type: 'text/csv'
});
const csvUrl = URL.createObjectURL(csvData);
link.href = csvUrl;
link.style = 'visibility:hidden';
link.download = `${fileName}.csv`; // this part will append the anchor tag and remove it after automatic click
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
this.renderMainTable = () => {
const {
columns
} = this.state;
const result = this.renderTable(columns, 'main');
return result;
};
this.renderLeftTable = () => {
const columns = this.state.columns.filter(column => {
return column.fixed === 'left';
});
return this.renderTable(columns, 'left');
};
this.renderRightTable = () => {
const columns = this.state.columns.filter(column => {
return column.fixed === 'right';
});
const baseLength = this.state.columns.length - columns.length;
return this.renderTable(columns, 'right', baseLength);
};
this.renderTable = (columns, type, baseLength = 0) => {
const {
classes,
resizable,
dragable,
onRowClick,
noData,
disableClickToFixColumn,
OrderProps,
TableCellProps,
TableRowProps
} = this.props;
const {
bodyHeight,
bodyRowHeight,
headRowHeight,
hasLeft,
hasRight,
data: bodyData,
sorts
} = this.state;
const width = type === 'main' ? '' : columns.reduce((pre, cur) => {
return pre + parseInt(cur.width);
}, 0);
const style = {
height: '100%',
overflowY: 'hidden',
width
};
const head = React.createElement(Head, {
OrderProps: OrderProps,
onSort: this.handleSort,
sorts: sorts,
baseLength: baseLength,
headRef: type === 'main' ? this.tableRefs.head : '',
columns: columns,
onResize: this.handleResize,
resizable: resizable,
dragable: dragable,
onDragStart: this.dragSatrt,
onDragEnd: this.dragEnd,
headRowHeight: headRowHeight,
onColumnFixChange: this.handleColumnFixSwitch,
disableClickToFixColumn: disableClickToFixColumn,
TableCellProps: TableCellProps,
TableRowProps: TableRowProps
});
const body = React.createElement(Body, {
syncTableRowHeight: this.syncTableRowHeight,
bodyRef: type === 'main' ? this.tableRefs.body : '',
data: bodyData,
columns: columns,
type: type,
height: `calc(100% - ${headRowHeight}px)`,
scroll: this.handlScrollY,
tableRef: this.tableRefs[type],
bodyRowHeight: bodyRowHeight,
bodyHeight: bodyHeight,
onRowClick: onRowClick,
noData: noData,
TableCellProps: TableCellProps,
TableRowProps: TableRowProps
});
const table = [head, body];
const className = classNames(classes[type], {
[classes.leftShadow]: type === 'left' && hasLeft
}, {
[classes.rightShadow]: type === 'right' && hasRight
});
const result = React.createElement("div", {
style: style,
ref: type === 'main' ? this.tableRefs.root : null,
className: className
}, table);
return result;
};
this.handleColumnFixSwitch = (index, fixed) => {
const {
columns
} = this.state;
columns[index].fixed = fixed;
this.setState({
columns: this.normalizeColumns(columns)
});
};
this.handlScrollY = (e, t) => {
const scrollTop = e.target.scrollTop;
t !== 'left' && (this.tableRefs.left.current.scrollTop = scrollTop);
t !== 'right' && (this.tableRefs.right.current.scrollTop = scrollTop);
t !== 'main' && (this.tableRefs.main.current.scrollTop = scrollTop);
};
this.handleScrollX = e => {
const scrollLeft = e.target.scrollLeft;
if (e.target !== this.tableRefs.root.current) {
return;
}
this.setTableShadow(scrollLeft);
};
this.handleChangePage = (event, page) => {
const {
TablePaginationProps
} = this.props;
const onChangePage = TablePaginationProps.onChangePage;
this.setState({
page
}, () => {
this.handlePaginteData();
});
onChangePage && onChangePage(event, page);
};
this.handleChangeRowsPerPage = event => {
const {
TablePaginationProps
} = this.props;
const onChangeRowsPerPage = TablePaginationProps.onChangeRowsPerPage;
this.setState({
rowsPerPage: event.target.value
}, () => {
this.handlePaginteData();
});
onChangeRowsPerPage && onChangeRowsPerPage(event);
};
this.setTableShadow = left => {
let {
scrollLeft,
clientWidth
} = this.tableRefs.root.current;
scrollLeft = left || scrollLeft;
const hasLeft = scrollLeft > 0;
const hasRight = this.tableRefs.main.current.clientWidth - clientWidth - scrollLeft > 0;
this.setState({
hasLeft,
hasRight
});
};
const _columns = this.normalizeColumns(_props.columns);
this.state = {
bodyHeight: 0,
hasLeft: false,
hasRight: true,
columns: _columns,
search: '',
page: _props.TablePaginationProps.page,
rowsPerPage: _props.TablePaginationProps.rowsPerPage,
data: _props.data,
sorts: this.getSortConfig(_props).sorts
};
}
componentDidMount() {
this.setTableShadow();
this.syncTableRowHeight();
this.handlePaginteData();
}
componentDidUpdate() {
const {
data,
paginatable,
searchable
} = this.props;
if (data !== this.propsCached.data) {
this.propsCached.data = data;
this.filteredData();
this.syncTableRowHeight(true);
}
if (searchable !== this.propsCached.searchable) {
this.propsCached.searchable = searchable;
this.filteredData();
}
if (paginatable !== this.propsCached.paginatable) {
this.propsCached.paginatable = paginatable;
this.filteredData();
}
}
render() {
const {
classes,
width,
paginatable,
searchable,
exportProps,
SearchProps,
sync,
title,
total
} = this.props;
const {
page,
rowsPerPage
} = this.state;
let h = 0;
if (this.tableRefs.toolbar.current) {
h += this.tableRefs.toolbar.current.clientHeight;
}
if (this.tableRefs.pagination.current) {
h += this.tableRefs.pagination.current.clientHeight;
}
const tbodyStyle = {
height: `calc(100% - ${h}px)`,
width
};
return React.createElement("div", {
className: classes.root
}, React.createElement("div", {
ref: this.tableRefs.toolbar
}, React.createElement(Toolbar, {
title: title,
onSearch: this.onSearch,
headRef: this.tableRefs.head,
bodyRef: this.tableRefs.body,
download: this.download,
searchable: searchable,
exportProps: exportProps,
SearchProps: SearchProps,
width: width
})), React.createElement("div", {
style: tbodyStyle,
className: classes.tbodyRoot,
onScroll: this.handleScrollX
}, [this.renderMainTable(), this.renderLeftTable(), this.renderRightTable()]), React.createElement("div", {
className: classes.footer,
style: {
width
}
}, React.createElement("div", {
className: classes.footerLeft
}, total), paginatable && React.createElement("div", {
ref: this.tableRefs.pagination,
className: classes.pagination
}, !sync ? React.createElement(Pagination, this.props.TablePaginationProps) : React.createElement(Pagination, _extends({}, this.props.TablePaginationProps, {
page: page,
rowsPerPage: rowsPerPage,
count: this.searchedData.data.length,
onChangePage: this.handleChangePage,
onChangeRowsPerPage: this.handleChangeRowsPerPage
})))));
}
}
process.env.NODE_ENV !== "production" ? AwesomeTable.propTypes = {
/**
* Override or extend the styles applied to the component.
* See [CSS API](#css-api) below for more details.
*/
classes: PropTypes.object.isRequired,
/**
* Columns of table
*/
columns: PropTypes.shape({
dataIndex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
fixed: PropTypes.oneOf(['left', 'right']),
key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
order: PropTypes.string,
render: PropTypes.func,
renderTitle: PropTypes.func,
resizable: PropTypes.bool,
sortable: PropTypes.bool,
title: PropTypes.node,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
}),
/**
* Data record array to be displayed
*/
data: PropTypes.array.isRequired,
/**
* Properties applied to the TablePagination Component
*/
disableClickToFixColumn: PropTypes.bool,
/**
* export config
*/
dragable: PropTypes.bool,
/**
* Properties applied to the Search Component
*/
exportProps: PropTypes.shape({
type: PropTypes.oneOf(['csv'])
}),
/**
* if true, it will be a paginatable table
*/
height: PropTypes.oneOfType([PropTypes.number, PropTypes.number]),
/**
* if true, it will be a searchable table
*/
noData: PropTypes.element,
/**
* if true, all the columns is resizable
*/
onColDrag: PropTypes.func,
/**
* if true, all the columns is dragable
*/
onRowClick: PropTypes.func,
/**
* table width
* @ignore
*/
OrderProps: PropTypes.shape({
multiple: PropTypes.bool,
onChangeOrder: PropTypes.func
}),
/**
* table height
* @ignore
*/
paginatable: PropTypes.bool,
/**
* Callback fired when you drag the column
*/
resizable: PropTypes.bool,
/**
* Callback fired when you click the table row
*/
searchable: PropTypes.bool,
/**
* if sync is true, pagination and search will be automatical.
* you needn't to do these things by yourself
*/
SearchProps: PropTypes.object,
/**
* The title of table
*/
sync: PropTypes.bool,
/**
* render when data length is 0
*/
TableCellProps: PropTypes.object,
/**
* @ignore
*/
TablePaginationProps: PropTypes.object,
/**
* Properties applied to the Order
*/
TableRowProps: PropTypes.object,
/**
* total
*/
title: PropTypes.node,
/**
* Properties applied to the TableCell element.
*/
total: PropTypes.element,
/**
* Properties applied to the TableRow element.
*/
width: PropTypes.oneOfType([PropTypes.number, PropTypes.number])
} : void 0;
AwesomeTable.defaultProps = {
TablePaginationProps: {
rowsPerPage: 10,
page: 0
},
data: [],
width: '100%',
// height: 'auto',
SearchProps: {
isDark: true,
floatRight: true
},
paginatable: false,
searchable: false,
resizable: false,
dragable: false,
sync: false,
noData: React.createElement(NoData, null),
disableClickToFixColumn: true,
OrderProps: {
multiple: false,
onChangeOrder: (...arg) => console.log(arg)
}
};
export default withStyles(styles, {
withTheme: true
})(AwesomeTable);