@yncoder/element-react
Version:
Element UI for React
456 lines (392 loc) • 13.8 kB
JSX
// @flow
import * as React from 'react';
import { Component, PropTypes } from '../../libs';
import local from '../locale';
import TableLayout from './TableLayout';
import type {
TableStoreProps,
TableStoreState,
Column,
_Column
} from './Types';
import normalizeColumns from './normalizeColumns';
import { getLeafColumns, getValueByPath, getColumns, convertToRows, getRowIdentity } from "./utils";
let tableIDSeed = 1;
function filterData(data, columns) {
return columns.reduce((preData, column) => {
const { filterable, filterMultiple, filteredValue, filterMethod } = column;
if (filterable) {
if (filterMultiple && Array.isArray(filteredValue) && filteredValue.length) {
return preData.filter(_data => filteredValue.some(value => filterMethod(value, _data)))
} else if (filteredValue) {
return preData.filter(_data => filterMethod(filteredValue, _data));
}
}
return preData;
}, data);
}
export default class TableStore extends Component<TableStoreProps, TableStoreState> {
static propTypes = {
style: PropTypes.object,
columns: PropTypes.arrayOf(PropTypes.object),
data: PropTypes.arrayOf(PropTypes.object),
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
maxHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
stripe: PropTypes.bool,
border: PropTypes.bool,
fit: PropTypes.bool,
showHeader: PropTypes.bool,
highlightCurrentRow: PropTypes.bool,
currentRowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.string)]),
rowClassName: PropTypes.func,
rowStyle: PropTypes.func,
rowKey: PropTypes.oneOfType([PropTypes.func, PropTypes.string,]),
emptyText: PropTypes.string,
defaultExpandAll: PropTypes.bool,
expandRowKeys:PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
defaultSort: PropTypes.shape({ prop: PropTypes.string, order: PropTypes.oneOf(['ascending', 'descending']) }),
tooltipEffect:PropTypes.oneOf(['dark', 'light']),
showSummary: PropTypes.bool,
sumText: PropTypes.string,
summaryMethod: PropTypes.func,
onSelect: PropTypes.func,
onSelectAll: PropTypes.func,
onSelectChange: PropTypes.func
};
static defaultProps = {
data: [],
showHeader: true,
stripe: false,
fit: true,
emptyText: local.t('el.table.emptyText'),
defaultExpandAll: false,
highlightCurrentRow: false,
showSummary: false,
sumText: local.t('el.table.sumText'),
};
static childContextTypes = {
tableStore: PropTypes.any,
};
getChildContext(): Object {
return {
tableStore: this,
}
}
constructor(props: TableStoreProps) {
super(props);
this.state = {
fixedColumns: null, // left fixed columns in _columns
rightFixedColumns: null, // right fixed columns in _columns
columnRows: null, // columns to render header
columns: null, // contain only leaf column
isComplex: null, // whether some column is fixed
expandingRows: [],
hoverRow: null,
currentRow: null,
selectable: null,
selectedRows: null,
sortOrder: null,
sortColumn: null,
};
[
'toggleRowSelection',
'toggleAllSelection',
'clearSelection',
'setCurrentRow',
].forEach((fn) => {
this[fn] = this[fn].bind(this);
});
this._isMounted = false;
}
componentWillMount() {
this.updateColumns(getColumns(this.props));
this.updateData(this.props);
this._isMounted = true;
}
componentWillReceiveProps(nextProps: TableStoreProps) {
const { data } = this.props;
const nextColumns = getColumns(nextProps);
if (getColumns(this.props) !== nextColumns) {
this.updateColumns(nextColumns);
}
if (data !== nextProps.data) {
this.updateData(nextProps);
}
}
get isAllSelected(): boolean {
const { currentRowKey, rowKey } = this.props;
const { selectedRows, data, selectable } = this.state;
const selectableData = selectable ? data.filter((row, index) => selectable(row, index)) : data;
if (!selectableData.length) {
return false;
}
if (Array.isArray(currentRowKey)) {
return selectableData.every(data => currentRowKey.includes(getRowIdentity(data, rowKey)));
}
return selectedRows && selectedRows.length === selectableData.length;
}
// shouldComponentUpdate(nextProps) {
// const propsKeys = Object.keys(this.props);
// const nextPropsKeys = Object.keys(nextProps);
//
// if (propsKeys.length !== nextPropsKeys.length) {
// return true;
// }
// for (const key of propsKeys) {
// if (this.props[key] !== nextProps[key]) {
// return true;
// }
// }
// return false;
// }
updateColumns(columns: Array<Column | Object>) {
let _columns = normalizeColumns(columns, tableIDSeed++);
const fixedColumns = _columns.filter(column => column.fixed === true || column.fixed === 'left');
const rightFixedColumns = _columns.filter(column => column.fixed === 'right');
let selectable;
if (_columns[0] && _columns[0].type === 'selection') {
selectable = _columns[0].selectable;
if (fixedColumns.length && !_columns[0].fixed) {
_columns[0].fixed = true;
fixedColumns.unshift(_columns[0]);
}
}
_columns = [].concat(fixedColumns, _columns.filter(column => !column.fixed), rightFixedColumns);
this.setState(Object.assign(this.state || {}, {
fixedColumns,
rightFixedColumns,
columnRows: convertToRows(_columns),
columns: getLeafColumns(_columns),
isComplex: fixedColumns.length > 0 || rightFixedColumns.length > 0,
selectable
}));
}
updateData(props: TableStoreProps) {
const { data = [], defaultExpandAll, defaultSort } = props;
const { columns } = this.state;
const filteredData = filterData(data.slice(), columns);
let { hoverRow, currentRow, selectedRows, expandingRows } = this.state;
hoverRow = hoverRow && data.includes(hoverRow) ? hoverRow : null;
currentRow = currentRow && data.includes(currentRow) ? currentRow : null;
const [firstColumn = {}] = columns;
if (this._isMounted && data !== this.props.data && !firstColumn.reserveSelection) {
selectedRows = [];
} else {
selectedRows = selectedRows && selectedRows.filter(row => data.includes(row)) || [];
}
if (!this._isMounted) {
expandingRows = defaultExpandAll ? data.slice() : [];
} else {
expandingRows = expandingRows.filter(row => data.includes(row));
}
this.setState(Object.assign(this.state, {
data: filteredData,
filteredData,
hoverRow,
currentRow,
expandingRows,
selectedRows,
}));
if ((!this._isMounted || data !== this.props.data) && defaultSort) {
const { prop, order = 'ascending' } = defaultSort;
const sortColumn = columns.find(column => column.property === prop);
this.changeSortCondition(sortColumn, order, false);
} else {
this.changeSortCondition(null, null, false);
}
}
setHoverRow(index: number) {
if (!this.state.isComplex) return;
this.setState({
hoverRow: index
});
}
toggleRowExpanded(row: Object, rowKey: string | number) {
const { expandRowKeys } = this.props;
let { expandingRows } = this.state;
if (expandRowKeys) {
const isRowExpanding = expandRowKeys.includes(rowKey);
this.dispatchEvent('onExpand', row, !isRowExpanding);
return;
}
expandingRows = expandingRows.slice();
const rowIndex = expandingRows.indexOf(row);
if (rowIndex > -1) {
expandingRows.splice(rowIndex, 1);
} else {
expandingRows.push(row);
}
this.setState({
expandingRows
}, () => {
this.dispatchEvent('onExpand', row, rowIndex === -1);
});
}
isRowExpanding(row: Object, rowKey: string | number): boolean {
const { expandRowKeys } = this.props;
const { expandingRows } = this.state;
if (expandRowKeys) {
return expandRowKeys.includes(rowKey);
}
return expandingRows.includes(row);
}
setCurrentRow(row: Object) {
const { currentRowKey, rowKey } = this.props;
if (currentRowKey && !Array.isArray(currentRowKey)) {
this.dispatchEvent('onCurrentChange', getRowIdentity(row, rowKey), currentRowKey);
return;
}
const { currentRow: oldRow } = this.state;
this.setState({
currentRow: row
}, () => {
this.dispatchEvent('onCurrentChange', row, oldRow)
});
}
toggleRowSelection(row: Object, isSelected?: boolean) {
const { currentRowKey, rowKey } = this.props;
if (Array.isArray(currentRowKey)) {
const toggledRowKey = getRowIdentity(row, rowKey);
const rowIndex = currentRowKey.indexOf(toggledRowKey);
const newCurrentRowKey = currentRowKey.slice();
if (isSelected !== undefined) {
if (isSelected && rowIndex === -1) {
newCurrentRowKey.push(toggledRowKey);
} else if (!isSelected && rowIndex !== -1) {
newCurrentRowKey.splice(rowIndex, 1);
}
} else {
rowIndex === -1 ? newCurrentRowKey.push(toggledRowKey) : newCurrentRowKey.splice(rowIndex, 1)
}
this.dispatchEvent('onSelect', newCurrentRowKey, row);
this.dispatchEvent('onSelectChange', newCurrentRowKey);
return;
}
this.setState(state => {
const selectedRows = state.selectedRows.slice();
const rowIndex = selectedRows.indexOf(row);
if (isSelected !== undefined) {
if (isSelected) {
rowIndex === -1 && selectedRows.push(row);
} else {
rowIndex !== -1 && selectedRows.splice(rowIndex, 1);
}
} else {
rowIndex === -1 ? selectedRows.push(row) : selectedRows.splice(rowIndex, 1)
}
return { selectedRows };
}, () => {
this.dispatchEvent('onSelect', this.state.selectedRows, row);
this.dispatchEvent('onSelectChange', this.state.selectedRows);
});
}
toggleAllSelection() {
const { currentRowKey, rowKey } = this.props;
let { data, selectedRows, selectable } = this.state;
const allSelectableRows = selectable ? data.filter((data, index) => selectable(data, index)) : data.slice();
if (Array.isArray(currentRowKey)) {
const newCurrentRowKey = this.isAllSelected ? [] : allSelectableRows.map(row => getRowIdentity(row, rowKey));
this.dispatchEvent('onSelectAll', newCurrentRowKey);
this.dispatchEvent('onSelectChange', newCurrentRowKey);
return;
}
if (this.isAllSelected) {
selectedRows = [];
} else {
selectedRows = allSelectableRows;
}
this.setState({
selectedRows,
}, () => {
this.dispatchEvent('onSelectAll', selectedRows);
this.dispatchEvent('onSelectChange', selectedRows);
})
}
clearSelection() {
const { currentRowKey } = this.props;
if (Array.isArray(currentRowKey)) return;
this.setState({
selectedRows: [],
});
}
isRowSelected(row: Object, rowKey: string | number): boolean {
const { currentRowKey } = this.props;
const { selectedRows } = this.state;
if (Array.isArray(currentRowKey)) {
return currentRowKey.includes(rowKey);
}
return selectedRows.includes(row);
}
changeSortCondition(column: ?_Column, order: ?string, shouldDispatchEvent?: boolean = true) { if (!column) ({ sortColumn: column, sortOrder: order } = this.state)
const data = this.state.filteredData.slice();
if (!column) {
this.setState({
data
});
return;
}
const { sortMethod, property, sortable } = column;
let sortedData;
if (!order || sortable === 'custom') {
sortedData = data;
} else if (sortable && sortable !== 'custom') {
const flag = order === 'ascending' ? 1 : -1;
if (sortMethod) {
sortedData = data.sort((a, b) => sortMethod(a, b) ? flag : -flag);
} else {
sortedData = data.sort((a, b) => {
const aVal = getValueByPath(a, property);
const bVal = getValueByPath(b, property);
return aVal === bVal ? 0 : aVal > bVal ? flag : -flag;
});
}
}
let sortSet = () => {
shouldDispatchEvent && this.dispatchEvent('onSortChange',
column && order ?
{ column, prop: column.property, order } :
{ column: null, prop: null, order: null }
)
}
if (sortable && sortable !== 'custom') {
this.setState({
sortColumn: column,
sortOrder: order,
data: sortedData,
},sortSet());
} else if (sortable && sortable === 'custom') {
this.setState({
sortColumn: column,
sortOrder: order,
},sortSet())
}
}
toggleFilterOpened(column: _Column) {
column.filterOpened = !column.filterOpened;
this.forceUpdate();
}
changeFilteredValue(column: _Column, value: string | number) {
column.filteredValue = value;
const filteredData = filterData(this.props.data.slice(), this.state.columns);
this.setState(Object.assign(this.state, {
filteredData
}), () => {
this.dispatchEvent('onFilterChange', { [column.columnKey]: value })
});
this.changeSortCondition(null, null, false);
}
dispatchEvent(name: string, ...args: Array<any>) {
const fn = this.props[name];
fn && fn(...args);
}
render() {
const renderExpanded = (this.state.columns.find(column => column.type === 'expand') || {}).expandPannel;
return (
<TableLayout
{...this.props}
renderExpanded={renderExpanded}
tableStoreState={this.state}
/>
)
}
}